pe: basic GUI setup
Adds a file browser (selected files are not loaded yet) and a shell widget with an input system that is still better than the previous VT-100 emulation scheme (with locked modifiers mainly). A lot of small things still need to be done to make the UI functional.
This commit is contained in:
parent
4e529b5788
commit
fa6aa00dae
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -17,19 +17,13 @@
|
|||
#ifndef __PYTHONEXTRA_CONSOLE_H
|
||||
#define __PYTHONEXTRA_CONSOLE_H
|
||||
|
||||
#include <gint/keyboard.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* 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 */
|
||||
|
|
111
ports/sh/main.c
111
ports/sh/main.c
|
@ -14,13 +14,33 @@
|
|||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/fs.h>
|
||||
|
||||
#include <justui/jscene.h>
|
||||
#include <justui/jlabel.h>
|
||||
#include <justui/jfkeys.h>
|
||||
#include <justui/jfileselect.h>
|
||||
#include <justui/jpainted.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "py/objtuple.h"
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include "shell.h"
|
||||
|
||||
#define FUN_0(NAME) \
|
||||
MP_DEFINE_CONST_FUN_OBJ_0(modgint_ ## NAME ## _obj, modgint_ ## NAME)
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
|
||||
#include "py/mphal.h"
|
||||
#include "../../shared/readline/readline.h"
|
||||
#include "keymap.h"
|
||||
#include "shell.h"
|
||||
#include "console.h"
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <unistd.h>
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 <gint/timer.h>
|
||||
#include <gint/display.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// <console.h> 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 */
|
|
@ -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 <justui/jwidget-api.h>
|
||||
#include <gint/timer.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 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");
|
||||
}
|
|
@ -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 <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <gint/display.h>
|
||||
#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 */
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue