UiSpecifications

Requirements/Motivation/Presentation

Fast and flexible GUI engine.

  • All UI description (layout, keymap, etc…) must be data driven (declarative structure with simple operators).
  • System friendly (don't use EXX / EX AF)
  • All-terrain (mustn't use firmware). We may use firmware key manager in first version.
  • Screen-size agnostic (i.e. can be overscan or other custom format)
  • Textmode-like based for maximum efficiency.
  • But open for mixed mode (sprites, pixel-precise placement, proportional fonts)
  • Can handle mix of 1 to 4 bytes-wide fonts in any mode.

These 3 last points lead to the following design choice:

Grid-based layout. Typical grid unit: 1 byte * 8 lignes (mode 2 char).

We don't allow arbitrary position in the screen, in order to ensure full speed display (for text, notably).
But this doesn't qualify as mere text-mode, since :

  • Client may use graphical characters as well.
  • Pure bitmap can be display in a defined window. Bitmap refresh would be delegate to a third party.
  • Several font-width can be mixed, so we don't have the correspondance 1 gridcell = 1 textchar.

Actually, the ability to use sub-byte resolution is left to the discretion of the client, since it can provide custom set_cursor and write_char.

Work Zone in RAM.

Placed in bank, possibly at fixed address. But not at fixed bank!
Engine and client must use a common memory manager for proper bank connection (so one's bank isn't unknowingly disconnected).

UI Case study

Mission: find a way to encode the UI logic of this particular example.

Considerate the following view :

     Channel A      Channel B      Channel C
00 | C#4 01 F V23 | C#5 02 F --- | --- -- - --- |
01 | E 4 01 E V23 | --- -- - --- | --- -- - --- |
02 | G#5 01 D V23 | --- -- - V12 | --- -- - --- |
03 | C#4 01 F V23 | --- -- - --- | --- -- - --- |
04 | E 4 01 E V23 | --- -- - V45 | --- -- - --- |
05 | G#5 01 D V23 | --- -- - --- | --- -- - --- |
06 | C#4 01 F V23 | --- -- - --- | --- -- - --- |
07 | E 4 01 E V23 | --- -- - --- | --- -- - --- |
08 | G#5 01 D V23 | B 5 02 - --- | --- -- - --- |
09 | C#4 01 F V23 | --- -- - --- | --- -- - --- |
10 | E 4 01 E V23 | --- -- - V12 | --- -- - --- |
11 | G#5 01 D V23 | --- -- - --- | --- -- - --- |
12 | C#4 01 F V23 | C#6 02 - --- | --- -- - --- |
13 | E 4 01 E V23 | --- -- - --- | --- -- - --- |
14 | G#5 01 D V23 | --- -- - V12 | --- -- - --- |
15 | C#4 01 F V23 | --- -- - --- | --- -- - --- |

Lets numerate cols for a phrase row :

0123456789AB
C#4 01 F V23

Then the UI logic must follow this rules :

  • Up & down navigation triggers scroll of the whole line (the 3 phrases).
    • This should be done via a generic 'table' widget.
  • f0/f. insert/delete a row in the only phrase we are in.
    • But this may need a whole display refresh, since the phrase might appear in different channel, possibly shifted.
  • Left & right cursor should navigate between columns 0,2,4,7,9,A,B. and then between channels.
    • This may be done by defining fields (ie Note, Octave, Instrument#, Volume, Effect, param1, param2 with their respective types) and separators.
  • In Note & Octave fields, we may play notes (e.g. '2' = C#) or clear note.
    • Playing note would also set Instrument#
    • Once again, the change may impact other channels, if the same phrase is re-used.
  • In Octave field, we can change octave! With Qwerty keyboards, '2' would change
  • In Instrument# field, we may enter an hexa number.
  • […] To Be Continued.

Some of the logic (ie detection of what should be refreshed) can be embedded in custom code called for data update.

Closer look

Instrument field.

Simple example, but yet already rich:

  • Numeric field within given range (e.g. 01-63)
  • Can be absent. This absence may be graphicly represented by '—'.

We need a getter to pick the actual value given current row/phrase, and a setter to update upon change. The former may use :

  • select_phrase
  • select_instrument
  • get_instrument

We still need to decide how parameters such row and phrase are communicated.

1/ Parameters passed from widget to getter (rejected)

The issue here is that widget is a field inside a structure inside the table cell.

2/ Parameters asked from getter (rejected)

To circumvent previous issue, we may explicitly ask current_row: widgets without 'row' notion will ask theirs parent.
Rejected because it's overkill and doesn't fit well the model: we have to (re)select (song's) row for each field, whereas it's likely to be the same row until all fields are read.

3/ Song's row is selected whenever table's row is changed (accepted)

We attach an 'on_enter_row' event to table widget, responsible for selecting the appropriate song's row. Then this row remains implicit.

Complete usage example

#include "gedeihen.o"

   org &9000
   ent $

; Init and launch the GUI engine.
; Exit with ESC 

   ld hl,param_gui
   call gui_init

   ld hl,example_gui
   call c,gui_run
   ret

get_value
   ld a,(value)
   ret

set_value
   ld (value),a
   ret

value BYTE 0  ; Default value is undefined (out of admissible range)

param_gui
   BYTE end_of_params

exemple_gui
; Actually a single field, which should be displayed at top-left
   BYTE NumericWidget
   BYTE range_min, 1
   BYTE range_max, 63
   BYTE optionnal, 1
   BYTE access:WORD get_value,set_value
   BYTE end_of_params

Specification

WIP! Draft inspired by case study below.

Widget := TableWidget | FieldWidget | StructureWidget
TableWidget := (Header, [Widget,Separator]) ; Liste typée
StructureWidget := [(Widget,Separator)] ; Aggregat de widget

… to be continued …

Concrete exemple

channelsView WORD TableWidget
;header
             WORD None
             WORD TextWidget:BYTE "Channel A",0
             WORD TextWidget;BYTE "Channel B",0
             WORD TextWidget:BYTE "Channel C",0
             WORD EndOfList
;Colums
             WORD TableRowNumber, TextWidget:BYTE " | ", 0
             WORD CustomWidget, phraseRow, TextWidget:BYTE " | ", 0
             WORD CustomWidget, phraseRow, TextWidget:BYTE " | ", 0
             WORD CustomWidget, phraseRow, TextWidget:BYTE " | ", 0
             WORD EndOfList
phraseRow
             WORD StructureWidget

TODO: replace WORD by BYTE (we don't plain more than 256 widget types !)

… to be continued …

Routines reference.

All parameters are passed in via a table. Parameters with default are optionnal.

gui_init

Initialize the gui engine, allowing to plug custom I/O routines (for key scan and display).

V1: assume a standard screen starting at &c000.
TODO : allow to define screen offset and dimensions.

in: hl=pointer to list of parameters.
  * read_key.   Routine to be used for key scanning. Default=km_read_key (&bb1b)
  * set_cursor. Routine to be used to set horizontal and vertical position. Default=txt_set_cursor (&bb75)
  * write_char. Routine to be used for char display. Will update position. Default=txt_wr_char (&bb5d)
  * write_text. Routine to be used for (NT) text display. These one isn't stricly needed but available for optimization purpose. Default to looping on output_char.

out: Carry if ok.

gui_run

Launch the engine.
V1: exit by ESC.
TODO: exit by CONTROL-SHIFT-ESC, since ESC will be used to cancel action or to exit some screen.

in: hl: pointer to root widget.

Widget references.

—- WIP!!

numericWidget

Params:
* type (enum): default = unsigned byte
Note: must be passed before other parameters depending on this type.
* range_min (type): default = type's min
* range_max (type): default = type's max
* optional (bool): default = false. Allow the field to be unset.
* access (2 words): addresses of getter and setter routines.
Sauf mention contraire, le contenu de cette page est protégé par la licence Creative Commons Attribution-ShareAlike 3.0 License