diff --git a/ports/fx9860g3/Makefile b/ports/fx9860g3/Makefile index 0ffbe84c2..dcc998d10 100644 --- a/ports/fx9860g3/Makefile +++ b/ports/fx9860g3/Makefile @@ -1,7 +1,7 @@ include ../../py/mkenv.mk SH_CFLAGS := -DFX9860G -SH_LDFLAGS := -T fx9860g.ld -lm -lgint-fx -lc -lgint-fx -lgcc +SH_LDFLAGS := -T fx9860g.ld -ljustui-fx -lm -lgint-fx -lc -lgint-fx -lgcc all: PythonExtra.g1a diff --git a/ports/fxcg50/Makefile b/ports/fxcg50/Makefile index 3ede7876c..525bf3942 100644 --- a/ports/fxcg50/Makefile +++ b/ports/fxcg50/Makefile @@ -1,7 +1,7 @@ include ../../py/mkenv.mk SH_CFLAGS := -DFXCG50 -SH_LDFLAGS := -T fxcg50.ld -lm -lgint-cg -lc -lgint-cg -lgcc +SH_LDFLAGS := -T fxcg50.ld -ljustui-cg -lm -lgint-cg -lc -lgint-cg -lgcc all: PythonExtra.g3a diff --git a/ports/sh/Makefile b/ports/sh/Makefile index 45f7a655b..e30ce684e 100644 --- a/ports/sh/Makefile +++ b/ports/sh/Makefile @@ -16,7 +16,7 @@ SRC_C = \ ports/sh/modgint.c \ ports/sh/modtime.c \ ports/sh/mphalport.c \ - ports/sh/shell.c \ + ports/sh/widget_shell.c \ shared/readline/readline.c \ shared/runtime/gchelper_generic.c \ shared/runtime/pyexec.c \ @@ -25,6 +25,7 @@ SRC_C = \ SRC_QSTR += \ shared/readline/readline.c \ + shared/runtime/pyexec.c \ ports/sh/modgint.c \ ports/sh/modtime.c \ diff --git a/ports/sh/console.c b/ports/sh/console.c index 81e253a5e..c303b6a3c 100644 --- a/ports/sh/console.c +++ b/ports/sh/console.c @@ -11,6 +11,10 @@ #include #include "console.h" +#include "py/mphal.h" +#include "../../shared/readline/readline.h" +#include "keymap.h" + //=== Dynamic console lines ===// bool console_line_init(console_line_t *line, int prealloc_size) @@ -136,6 +140,7 @@ console_t *console_create(int backlog_size) cons->lines = NULL; cons->line_count = 0; cons->backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH); + cons->render_needed = true; if(!console_new_line(cons)) { free(cons); return NULL; @@ -157,6 +162,7 @@ bool console_new_line(console_t *cons) return false; cons->line_count++; cons->cursor = 0; + cons->render_needed = true; /* Routinely clean the backlog every time a line is added. */ // console_clean_backlog(cons); @@ -201,6 +207,7 @@ bool console_write_block_at_cursor(console_t *cons, char const *str, int n) if(!console_line_insert(last_line, cons->cursor, str, round_size)) return false; cons->cursor += round_size; + cons->render_needed = true; if(round_size < n && !console_new_line(cons)) return false; n -= round_size; @@ -257,12 +264,15 @@ bool console_write_at_cursor(console_t *cons, char const *buf, int n) cons->cursor += (cons->cursor < last_line->size); } } + + cons->render_needed = true; } return true; } -void console_render(int x, int y, console_t *cons, int w, int lines) +void console_render(int x, int y, console_t const *cons, int w, int dy, + int lines) { int watermark = lines; @@ -277,12 +287,17 @@ void console_render(int x, int y, console_t *cons, int w, int lines) bool show_cursor = (i == cons->line_count - 1); if(watermark + line->render_lines > 0) - y = console_line_render(x, y, line, w, PE_CONSOLE_LINE_SPACING, - -watermark, show_cursor ? cons->cursor : -1); + y = console_line_render(x, y, line, w, dy, -watermark, + show_cursor ? cons->cursor : -1); watermark += line->render_lines; } } +void console_clear_render_flag(console_t *cons) +{ + cons->render_needed = false; +} + void console_destroy(console_t *cons) { for(int i = 0; i < cons->line_count; i++) @@ -290,3 +305,56 @@ void console_destroy(console_t *cons) free(cons->lines); free(cons); } + +//=== Input method ===// + +/* Features needed to bypass MicroPython's readline: + * Undefined MICROPY_HAL_HAS_VT100 + * Multi-line input + - Provide PS1 and PS2 + - Auto-indent + - TODO: How is it stored? + * History + - Let's use `MP_STATE_PORT(readline_hist)` for a start + - Keep track of history browsing state + - While at it do the zsh history search, which is goated + - Use readline_push_history() + * Cursor movement (fairly easy) + * Handle special inputs + - ^C, ^D, backspace + - Erase line (^K) + * Autocompletion + - Use mp_repl_autocomplete() which should hook just fine */ + +int console_key_event_to_char(key_event_t ev) +{ + int key = ev.key; + + /* (The following meanings are only for non-empty lines) */ + /* TODO: Check cons->cursor before triggering them */ + + if(key == KEY_LEFT && ev.shift) + return CHAR_CTRL_A; /* go-to-start-of-line */ + if(key == KEY_LEFT) + return CHAR_CTRL_B; /* go-back-one-char */ + if(key == KEY_ACON) + return CHAR_CTRL_C; /* cancel */ + if(key == KEY_DEL && !ev.shift) + return 8; /* delete-at-cursor */ + if(key == KEY_RIGHT && ev.shift) + return CHAR_CTRL_E; /* go-to-end-of-line */ + if(key == KEY_RIGHT) + return CHAR_CTRL_F; /* go-forward-one-char */ + if(key == KEY_DEL && ev.shift) + return CHAR_CTRL_K; /* kill from cursor to end-of-line */ + if(key == KEY_DOWN) + return CHAR_CTRL_N; /* go to next line in history */ + if(key == KEY_UP) + return CHAR_CTRL_P; /* go to previous line in history */ + if(key == KEY_EXIT) + return CHAR_CTRL_D; /* eof */ + if(key == KEY_EXE) + return '\r'; + + return keymap_translate(key, ev.shift, ev.alpha); +} diff --git a/ports/sh/console.h b/ports/sh/console.h index 70634eac1..04b307d87 100644 --- a/ports/sh/console.h +++ b/ports/sh/console.h @@ -17,19 +17,13 @@ #ifndef __PYTHONEXTRA_CONSOLE_H #define __PYTHONEXTRA_CONSOLE_H +#include #include /* Maximum line length, to ensure the console can threshold its memory usage while cleaning only entire lines. Lines longer than this get split. */ #define PE_CONSOLE_LINE_MAX_LENGTH 1024 -/* Line spacing in the console */ -#ifdef FX9860G -# define PE_CONSOLE_LINE_SPACING 8 -#else -# define PE_CONSOLE_LINE_SPACING 13 -#endif - //=== Dynamic console lines ===// typedef struct @@ -84,6 +78,9 @@ typedef struct /* Cursor position within the last line. */ int cursor; + /* Whether new data has been added and a frame should be rendered. */ + bool render_needed; + } console_t; /* Create a new console with the specified backlog size. */ @@ -105,8 +102,18 @@ bool console_write_block_at_cursor(console_t *cons, char const *str, int n); bool console_write_at_cursor(console_t *cons, char const *str, int n); /* TODO: Expand this function */ -void console_render(int x, int y, console_t *cons, int w, int lines); +void console_render(int x, int y, console_t const *cons, int w, int dy, + int lines); + +void console_clear_render_flag(console_t *cons); void console_destroy(console_t *cons); +//=== Input method ===// + +/* Interpret a key event into a terminal input. This is a pretty raw input + method with no shift/alpha lock, kept for legacy as a VT-100-style terminal + emulator. */ +int console_key_event_to_char(key_event_t ev); + #endif /* __PYTHONEXTRA_CONSOLE_H */ diff --git a/ports/sh/main.c b/ports/sh/main.c index 70e430f81..f1aa8fd8f 100644 --- a/ports/sh/main.c +++ b/ports/sh/main.c @@ -14,13 +14,33 @@ #include #include #include + +#include +#include +#include +#include +#include + #include #include #include #include +#include #include "console.h" -#include "shell.h" +#include "widget_shell.h" + +//--- + +#include "py/mphal.h" +#include "py/repl.h" +#include "genhdr/mpversion.h" + +#ifdef FX9860G +#define _(fx, cg) (fx) +#else +#define _(fx, cg) (cg) +#endif static ssize_t stdouterr_write(void *data, void const *buf, size_t size) { @@ -36,9 +56,33 @@ fs_descriptor_type_t stdouterr_type = { .close = NULL, }; +/* The global terminal. */ +console_t *pe_shell_console; + +static bool strendswith(char const *str, char const *suffix) +{ + size_t l1 = strlen(str); + size_t l2 = strlen(suffix); + + return l1 >= l2 && strcmp(str + l1 - l2, suffix) == 0; +} + +static bool py_file_filter(struct dirent const *ent) +{ + if(!jfileselect_default_filter(ent)) + return false; + + if(ent->d_type == DT_REG && !strendswith(ent->d_name, ".py")) + return false; + + return true; +} + int main(int argc, char **argv) { - pe_shell_init(); + //=== Init sequence ===// + + pe_shell_console = console_create(8192); /* Set up standard streams */ close(STDOUT_FILENO); @@ -68,13 +112,70 @@ int main(int argc, char **argv) // * (keep the OS heap for normal malloc()) mp_init(); - // Start a normal REPL; will exit when ctrl-D is entered on a blank line. - pyexec_friendly_repl(); + /* Run REPL manually */ + pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + pyexec_event_repl_init(); + + //=== GUI setup ===// + + jscene *scene = jscene_create_fullscreen(NULL); + jlabel *title = jlabel_create("PythonExtra", scene); + jwidget *stack = jwidget_create(scene); + jfkeys *fkeys = jfkeys_create("/FILES;/SHELL;;;;", scene); + (void)fkeys; + + jwidget_set_background(title, C_BLACK); + jlabel_set_text_color(title, C_WHITE); + jwidget_set_stretch(title, 1, 0, false); + jwidget_set_padding(title, _(1, 3), _(2, 6), _(1, 3), _(2, 6)); + + jlayout_set_vbox(scene)->spacing = _(1, 3); + jlayout_set_stack(stack); + jwidget_set_padding(stack, 0, 6, 0, 6); + jwidget_set_stretch(stack, 1, 1, false); + + /* Filesystem tab */ + jfileselect *fileselect = jfileselect_create(stack); + jfileselect_set_filter(fileselect, py_file_filter); + jfileselect_set_show_file_size(fileselect, true); + jwidget_set_stretch(fileselect, 1, 1, false); + + /* Shell tab */ + widget_shell *shell = widget_shell_create(pe_shell_console, stack); + widget_shell_set_line_spacing(shell, _(1, 3)); + jwidget_set_stretch(shell, 1, 1, false); + + /* Initial state */ + jfileselect_browse(fileselect, "/"); + jscene_show_and_focus(scene, fileselect); + + //=== Event handling ===// + + while(1) { + jevent e = jscene_run(scene); + + if(e.type == JSCENE_PAINT) { + dclear(C_WHITE); + jscene_render(scene); + dupdate(); + } + + if(e.type != JWIDGET_KEY || e.key.type == KEYEV_UP) + continue; + int key = e.key.key; + + if(key == KEY_F1) + jscene_show_and_focus(scene, fileselect); + if(key == KEY_F2) + jscene_show_and_focus(scene, shell); + } + + //=== Deinitialization ===// // Deinitialise the runtime. gc_sweep_all(); mp_deinit(); - pe_shell_deinit(); + console_destroy(pe_shell_console); return 0; } diff --git a/ports/sh/modgint.c b/ports/sh/modgint.c index 0fa46d33b..f623ef22a 100644 --- a/ports/sh/modgint.c +++ b/ports/sh/modgint.c @@ -13,7 +13,6 @@ #include "py/objtuple.h" #include #include -#include "shell.h" #define FUN_0(NAME) \ MP_DEFINE_CONST_FUN_OBJ_0(modgint_ ## NAME ## _obj, modgint_ ## NAME) diff --git a/ports/sh/mpconfigport.h b/ports/sh/mpconfigport.h index 846739923..8f47c675b 100644 --- a/ports/sh/mpconfigport.h +++ b/ports/sh/mpconfigport.h @@ -21,6 +21,7 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +#define MICROPY_REPL_EVENT_DRIVEN (1) /* Other features that we select against MICROPY_CONFIG_ROM_LEVEL */ #define MICROPY_PY_FSTRINGS (1) /* in EXTRA_FEATURES */ diff --git a/ports/sh/mphalport.c b/ports/sh/mphalport.c index a9af9a1a9..81fd68666 100644 --- a/ports/sh/mphalport.c +++ b/ports/sh/mphalport.c @@ -5,9 +5,7 @@ //---------------------------------------------------------------------------// #include "py/mphal.h" -#include "../../shared/readline/readline.h" -#include "keymap.h" -#include "shell.h" +#include "console.h" #include #include #include @@ -16,33 +14,7 @@ int mp_hal_stdin_rx_chr(void) { while(1) { key_event_t ev = getkey(); - int key = ev.key; - - /* (The following meanings are only for non-empty lines) */ - /* TODO: Check cons->cursor before triggering them */ - - if(key == KEY_LEFT && ev.shift) - return CHAR_CTRL_A; /* go-to-start-of-line */ - if(key == KEY_LEFT) - return CHAR_CTRL_B; /* go-back-one-char */ - if(key == KEY_ACON) - return CHAR_CTRL_C; /* cancel */ - if(key == KEY_DEL && !ev.shift) - return 8; /* delete-at-cursor */ - if(key == KEY_RIGHT && ev.shift) - return CHAR_CTRL_E; /* go-to-end-of-line */ - if(key == KEY_RIGHT) - return CHAR_CTRL_F; /* go-forward-one-char */ - if(key == KEY_DEL && ev.shift) - return CHAR_CTRL_K; /* kill from cursor to end-of-line */ - if(key == KEY_DOWN) - return CHAR_CTRL_N; /* go to next line in history */ - if(key == KEY_UP) - return CHAR_CTRL_P; /* go to previous line in history */ - if(key == KEY_EXE) - return '\r'; - - uint32_t code_point = keymap_translate(key, ev.shift, ev.alpha); + int code_point = console_key_event_to_char(ev); if(code_point != 0) return code_point; } @@ -52,6 +24,4 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { int r = write(STDOUT_FILENO, str, len); (void)r; - - pe_shell_schedule_update(); } diff --git a/ports/sh/shell.c b/ports/sh/shell.c index 4a7579660..aea8f80d9 100644 --- a/ports/sh/shell.c +++ b/ports/sh/shell.c @@ -1,69 +1,18 @@ -//---------------------------------------------------------------------------// -// ____ PythonExtra // -//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. // -//.-'` `---` ' License: MIT (except some files; see LICENSE) // -//---------------------------------------------------------------------------// - -#include "shell.h" -#include -#include - -/* The global terminal. */ -console_t *pe_shell_console; -/* Timer handle for the refresh clock. */ -static int pe_shell_timer = -1; -/* Whether there is new shell data to be shown on the next frame. */ -static bool pe_shell_update = true; - -static void pe_shell_draw(void) -{ - dclear(C_WHITE); - -#ifdef FX9860G - int rows = 8; - console_render(1, 1, pe_shell_console, DWIDTH-2, rows); -#else - dprint(3, 3, C_BLACK, "PythonExtra, very much WIP :)"); - dline(2, 16, DWIDTH-3, 16, C_BLACK); - int rows = 12; - console_render(3, 20, pe_shell_console, DWIDTH-6, rows); - int y = 20 + PE_CONSOLE_LINE_SPACING * rows; - dline(2, y, DWIDTH-3, y, C_BLACK); -#endif - - dupdate(); -} - -static int pe_shell_timer_handler(void) -{ - if(pe_shell_update) { - pe_shell_draw(); - pe_shell_update = false; - } - return TIMER_CONTINUE; -} - -void pe_shell_schedule_update(void) -{ - pe_shell_update = true; -} +// The shell consists of a terminal-emulating `console_t` along with some input +// UI. During execution, the shell is redrawn only (1) when new data is printed +// or requested, and (2) a fixed refresh timer hits. Condition 1 avoids +// needless updates for non-printing programs, while condition 2 prevents +// performance issues for sequences that print one character at a time (which +// is quite common even within MicroPython builtins). +// +// If the built-in function gint.dupdate() is used, the shell also hands over +// display updates to "graphics mode", where only manual dupdate() calls push +// the VRAM. Switching to graphics mode cancels any scheduled shell updates. +// Graphics mode is exited once a new shell update is explicity scheduled, +// typically by a call to print() or input(). +/* TODO: Port graphics mode over to the JustUI setup */ void pe_shell_graphics_mode(void) { - pe_shell_update = false; -} - -void pe_shell_init(void) -{ - pe_shell_console = console_create(8192); - - pe_shell_timer = timer_configure(TIMER_ANY, 1000000 / PE_SHELL_FPS, - GINT_CALL(pe_shell_timer_handler)); - timer_start(pe_shell_timer); -} - -void pe_shell_deinit(void) -{ - timer_stop(pe_shell_timer); - console_destroy(pe_shell_console); + // pe_shell_update = false; } diff --git a/ports/sh/shell.h b/ports/sh/shell.h deleted file mode 100644 index 7813f16ad..000000000 --- a/ports/sh/shell.h +++ /dev/null @@ -1,51 +0,0 @@ -//---------------------------------------------------------------------------// -// ____ PythonExtra // -//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. // -//.-'` `---` ' License: MIT (except some files; see LICENSE) // -//---------------------------------------------------------------------------// -// pe.shell: Shell integration -// -// This header covers application-integration aspects of the shell. For the -// implementation of the console itself (input, escape sequences, etc). see -// instead. -// -// The shell consists of a terminal-emulating `console_t` along with some input -// UI. During execution, the shell is redrawn only (1) when new data is printed -// or requested, and (2) a fixed refresh timer hits. Condition 1 avoids -// needless updates for non-printing programs, while condition 2 prevents -// performance issues for sequences that print one character at a time (which -// is quite common even within MicroPython builtins). -// -// If the built-in function gint.dupdate() is used, the shell also hands over -// display updates to "graphics mode", where only manual dupdate() calls push -// the VRAM. Switching to graphics mode cancels any scheduled shell updates. -// Graphics mode is exited once a new shell update is explicity scheduled, -// typically by a call to print() or input(). -//--- - -#ifndef __PYTHONEXTRA_SHELL_H -#define __PYTHONEXTRA_SHELL_H - -#include "console.h" - -/* Shell frame frequency, ie. cap on the number of shell redraws per second. */ -#define PE_SHELL_FPS 30 - -/* The terminal. */ -extern console_t *pe_shell_console; - -/* Schedule a redraw of the shell interface at the next shell frame. This is - called whenever data is printed into the shell or input() is used. The - scheduled redraw might be canceled by switching to graphics mode before the - frame timer fires. */ -void pe_shell_schedule_update(void); - -/* Switch to graphics mode. This cancels pending shell updates. */ -void pe_shell_graphics_mode(void); - -//=== Internal functions ===// - -void pe_shell_init(void); -void pe_shell_deinit(void); - -#endif /* __PYTHONEXTRA_SHELL_H */ diff --git a/ports/sh/widget_shell.c b/ports/sh/widget_shell.c new file mode 100644 index 000000000..0239f5d98 --- /dev/null +++ b/ports/sh/widget_shell.c @@ -0,0 +1,219 @@ +//---------------------------------------------------------------------------// +// ____ PythonExtra // +//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. // +//.-'` `---` ' License: MIT (except some files; see LICENSE) // +//---------------------------------------------------------------------------// + +#include "widget_shell.h" +#include +#include +#include + +/* Type identified for widget_shell */ +static int widget_shell_id = -1; + +//=== Modifier states ===// + +enum { + MOD_IDLE, /* Not active */ + MOD_INSTANT, /* Instant-loaded but not yet used */ + MOD_INSTANT_USED, /* Instant-loaded and has been used */ + + MOD_LOCKED, /* Locked */ + MOD_LOCKED_INSTANT, /* Locked and instant-loaded but not used */ + MOD_LOCKED_INSTANT_USED, /* Locked and instant-loaded and used */ +}; + +/* Handle a key press/release for a modifier */ +static int mod_down(int state) +{ + if(state == MOD_IDLE) + return MOD_INSTANT; + if(state == MOD_LOCKED) + return MOD_LOCKED_INSTANT; + return state; +} +static int mod_up(int state) +{ + if(state == MOD_INSTANT) + return MOD_LOCKED; + if(state == MOD_INSTANT_USED) + return MOD_IDLE; + if(state == MOD_LOCKED_INSTANT) + return MOD_IDLE; + if(state == MOD_LOCKED_INSTANT_USED) + return MOD_LOCKED; + return state; +} +/* Handle a press for another key */ +static int mod_down_other(int state) +{ + if(state == MOD_INSTANT) + return MOD_INSTANT_USED; + if(state == MOD_LOCKED_INSTANT) + return MOD_LOCKED_INSTANT_USED; + return state; +} +/* Whether a modifier is active */ +static bool mod_active(int state) +{ + return state == MOD_LOCKED || state == MOD_INSTANT + || state == MOD_INSTANT_USED; +} + +//=== Shell widget ===// + +static int widget_shell_timer_handler(void *s0) +{ + widget_shell *s = s0; + + if(s->console && s->console->render_needed) + s->widget.update = true; + + return TIMER_CONTINUE; +} + +widget_shell *widget_shell_create(console_t *console, void *parent) +{ + if(widget_shell_id < 0) + return NULL; + + widget_shell *s = malloc(sizeof *s); + if(!s) + return NULL; + + s->timer_id = timer_configure(TIMER_ANY, 1000000 / WIDGET_SHELL_FPS, + GINT_CALL(widget_shell_timer_handler, (void *)s)); + if(s->timer_id < 0) { + free(s); + return NULL; + } + + jwidget_init(&s->widget, widget_shell_id, parent); + + s->console = console; + s->font = dfont_default(); + s->color = C_BLACK; + s->line_spacing = 0; + s->lines = 0; + + s->shift = MOD_IDLE; + s->alpha = MOD_IDLE; + + timer_start(s->timer_id); + + return s; +} + +void widget_shell_set_text_color(widget_shell *s, int color) +{ + s->color = color; + s->widget.update = 1; +} + +void widget_shell_set_font(widget_shell *s, font_t const *font) +{ + s->font = font ? font : dfont_default(); + s->widget.dirty = 1; +} + +void widget_shell_set_line_spacing(widget_shell *s, int line_spacing) +{ + s->line_spacing = line_spacing; + s->widget.dirty = 1; +} + +//--- +// Polymorphic widget operations +//--- + +static void widget_shell_poly_csize(void *s0) +{ + widget_shell *s = s0; + int row_height = s->font->line_height; + int base_rows = 4; + + s->widget.w = DWIDTH / 2; + s->widget.h = row_height * base_rows + s->line_spacing * (base_rows - 1); +} + +static void widget_shell_poly_layout(void *s0) +{ + widget_shell *s = s0; + + int ch = jwidget_content_height(s); + int line_height = s->font->line_height + s->line_spacing; + s->lines = ch / line_height; +} + +static void widget_shell_poly_render(void *s0, int x, int y) +{ + widget_shell *s = s0; + int line_height = s->font->line_height + s->line_spacing; + + font_t const *old_font = dfont(s->font); + console_render(x, y, s->console, jwidget_content_width(s), line_height, + s->lines); + console_clear_render_flag(s->console); + dfont(old_font); +} + +static bool widget_shell_poly_event(void *s0, jevent e) +{ + widget_shell *s = s0; + + if(e.type != JWIDGET_KEY) + return false; + key_event_t ev = e.key; + + if(ev.key == KEY_SHIFT) { + s->shift = ev.type == KEYEV_UP ? mod_up(s->shift) : mod_down(s->shift); + return true; + } + if(ev.key == KEY_ALPHA) { + s->alpha = ev.type == KEYEV_UP ? mod_up(s->alpha) : mod_down(s->alpha); + return true; + } + + if(ev.type == KEYEV_UP) + return false; + + ev.mod = true; + ev.shift = mod_active(s->shift); + ev.alpha = mod_active(s->alpha); + + s->shift = mod_down_other(s->shift); + s->alpha = mod_down_other(s->alpha); + + /* TODO: Handle input events better in the shell widget! */ + int c = console_key_event_to_char(ev); + /* TODO: Can widget_shell_poly_event please not call into MicroPython? */ + if(c != 0) { + pyexec_event_repl_process_char(c); + return true; + } + + return false; +} + +static void widget_shell_poly_destroy(void *s0) +{ + widget_shell *s = s0; + timer_stop(s->timer_id); +} + +/* widget_shell type definition */ +static jwidget_poly type_widget_shell = { + .name = "widget_shell", + .csize = widget_shell_poly_csize, + .layout = widget_shell_poly_layout, + .render = widget_shell_poly_render, + .event = widget_shell_poly_event, + .destroy = widget_shell_poly_destroy, +}; + +__attribute__((constructor)) +static void j_register_widget_shell(void) +{ + widget_shell_id = j_register_widget(&type_widget_shell, "jwidget"); +} diff --git a/ports/sh/widget_shell.h b/ports/sh/widget_shell.h new file mode 100644 index 000000000..81bfb3791 --- /dev/null +++ b/ports/sh/widget_shell.h @@ -0,0 +1,48 @@ +//---------------------------------------------------------------------------// +// ____ PythonExtra // +//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. // +//.-'` `---` ' License: MIT (except some files; see LICENSE) // +//---------------------------------------------------------------------------// +// pe.widget_shell: JustUI widget for the shell + +#ifndef __PYTHONEXTRA_WIDGET_SHELL_H +#define __PYTHONEXTRA_WIDGET_SHELL_H + +#include +#include +#include +#include "console.h" + +/* widget_shell: Multi-line Python shell input */ +typedef struct { + jwidget widget; + + /* Corresponding console */ + console_t *console; + + /* Rendering font */ + font_t const *font; + /* Text color */ + uint16_t color; + /* Extra line spacing */ + int8_t line_spacing; + + /* Internal information */ + int timer_id; + uint16_t lines; + int shift, alpha; + +} widget_shell; + +/* Update frequency, ie. cap on the number of shell redraws per second. */ +#define WIDGET_SHELL_FPS 30 + +/* widget_shell_create(): Create a shell widget tied to a console */ +widget_shell *widget_shell_create(console_t *console, void *parent); + +/* Trivial properties */ +void widget_shell_set_text_color(widget_shell *shell, int color); +void widget_shell_set_font(widget_shell *shell, font_t const *font); +void widget_shell_set_line_spacing(widget_shell *shell, int line_spacing); + +#endif /* __PYTHONEXTRA_WIDGET_SHELL_H */ diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 2c9f1e80f..7438f25c9 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -400,7 +400,6 @@ STATIC int pyexec_friendly_repl_process_char(int c) { return 0; } else if (ret == CHAR_CTRL_B) { // reset friendly REPL - mp_hal_stdout_tx_str("\r\n"); mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); mp_hal_stdout_tx_str("\r\n");