mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-26 19:43:39 +01:00
version 1.0.0, I guess
This commit is contained in:
commit
120b33c9f3
38 changed files with 4181 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Build files
|
||||
build-fx
|
||||
build-cg
|
||||
|
||||
# GiteaPC support
|
||||
giteapc-config.make
|
||||
giteapc-config-*.make
|
||||
|
||||
# Developer's files
|
||||
*.sublime-*
|
51
CMakeLists.txt
Normal file
51
CMakeLists.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Just UI
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(JustUI VERSION 1.0 LANGUAGES C)
|
||||
find_package(Gint 2.1 REQUIRED)
|
||||
include(Fxconv)
|
||||
|
||||
configure_file(include/justui/config.h.in include/justui/config.h)
|
||||
|
||||
set(ASSETS_fx
|
||||
assets/input-modes-fx.png
|
||||
)
|
||||
set(ASSETS_cg
|
||||
assets/input-modes-cg.png
|
||||
)
|
||||
fxconv_declare_assets(${ASSETS_fx} ${ASSETS_cg})
|
||||
|
||||
set(NAME "justui-${FXSDK_PLATFORM}")
|
||||
add_library(${NAME} STATIC
|
||||
src/jwidget.c
|
||||
src/jlayout_box.c
|
||||
src/jlayout_stack.c
|
||||
src/jlayout_grid.c
|
||||
src/jlabel.c
|
||||
src/jscene.c
|
||||
src/jinput.c
|
||||
src/jpainted.c
|
||||
src/jfkeys.c
|
||||
src/vec.c
|
||||
src/keymap.c
|
||||
${ASSETS_${FXSDK_PLATFORM}}
|
||||
)
|
||||
|
||||
target_compile_options(${NAME} PUBLIC
|
||||
-Wall -Wextra -std=c11 -Os)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include"
|
||||
"${FXSDK_COMPILER_INSTALL}/include/openlibm")
|
||||
target_link_libraries(${NAME}
|
||||
Gint::Gint -lopenlibm)
|
||||
|
||||
install(TARGETS ${NAME}
|
||||
DESTINATION "${FXSDK_COMPILER_INSTALL}")
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
DESTINATION "${FXSDK_COMPILER_INSTALL}"
|
||||
FILES_MATCHING PATTERN "*.h")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/justui/config.h"
|
||||
DESTINATION "${FXSDK_COMPILER_INSTALL}/include/justui")
|
||||
install(FILES cmake/FindJustUI.cmake
|
||||
DESTINATION "${FXSDK_CMAKE_MODULE_PATH}")
|
24
README.md
Normal file
24
README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Just User Interfaces
|
||||
|
||||
JustUI is a "small" library to make GUIs on fx-9860G and fx-CG 50 with gint
|
||||
(and I mean small by GUI library standards). I've built it to improve user
|
||||
interfaces in [gintctl](https://gitea.planet-casio.com/Lephenixnoir/gintctl),
|
||||
but it's still documented enough that you can try it if you need GUIs.
|
||||
|
||||
Features include:
|
||||
|
||||
* Widget trees with parent/children ownership
|
||||
* Basic `jwidget` and derived types (`jlabel`, `jinput`, etc.)
|
||||
* A good layout system to determine the size and position of widgets
|
||||
* Low-effort custom widgets (`jpainted`) and completely custom widget types
|
||||
* Decent keyboard focus and event system
|
||||
|
||||
Screenshots: TODO
|
||||
|
||||
Documentation:
|
||||
|
||||
* [Widget types](doc/widgets.md)
|
||||
* [Widget hierarchy](doc/hierarchy.md)
|
||||
* [Space distribution and layout](doc/layout.md)
|
||||
* [Scenes and events](doc/scene.md) (TODO)
|
||||
* Everything else is explained in the headers.
|
10
TODO
Normal file
10
TODO
Normal file
|
@ -0,0 +1,10 @@
|
|||
* Implement grid layout
|
||||
* Other useful widgets
|
||||
|
||||
* Event subscriptions
|
||||
|
||||
* Simplify jlabel code using dfont_default()
|
||||
|
||||
Box layout:
|
||||
* align-items (don't always center along the cross axis)
|
||||
* justify-content (distribute empty space along the main axis)
|
8
assets/fxconv-metadata.txt
Normal file
8
assets/fxconv-metadata.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
input-modes-fx.png:
|
||||
name: j_img_input_modes
|
||||
type: bopti-image
|
||||
|
||||
input-modes-cg.png:
|
||||
name: j_img_input_modes
|
||||
type: bopti-image
|
||||
profile: p4
|
BIN
assets/input-modes-cg.png
Normal file
BIN
assets/input-modes-cg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 325 B |
BIN
assets/input-modes-fx.png
Normal file
BIN
assets/input-modes-fx.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 B |
19
cmake/FindJustUI.cmake
Normal file
19
cmake/FindJustUI.cmake
Normal file
|
@ -0,0 +1,19 @@
|
|||
include(FindSimpleLibrary)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package(Gint 2.1 REQUIRED)
|
||||
|
||||
find_simple_library("libjustui-${FXSDK_PLATFORM}.a" include/justui/config.h
|
||||
"J_VERSION" PATH_VAR J_PATH VERSION_VAR J_VERSION)
|
||||
|
||||
find_package_handle_standard_args(JustUI
|
||||
REQUIRED_VARS J_PATH J_VERSION
|
||||
VERSION_VAR J_VERSION)
|
||||
|
||||
if(JustUI_FOUND)
|
||||
add_library(JustUI::JustUI UNKNOWN IMPORTED)
|
||||
set_target_properties(JustUI::JustUI PROPERTIES
|
||||
IMPORTED_LOCATION "${J_PATH}"
|
||||
INTERFACE_LINK_OPTIONS -ljustui-${FXSDK_PLATFORM}
|
||||
IMPORTED_LINK_INTERFACE_LIBRARIES Gint::Gint)
|
||||
endif()
|
33
doc/hierarchy.md
Normal file
33
doc/hierarchy.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# JustUI: Widget hierarchy
|
||||
|
||||
## The tree hierarchy
|
||||
|
||||
Every widget (either of type `jwidget` or of a structure type that begins with
|
||||
a `jwidget`) has a parent. This relationship forms a tree. The widgets that
|
||||
have a common parent *w* are called the children of *w*.
|
||||
|
||||
The motivation for the widget hierarchy is to have groups of widgets behave as
|
||||
one. A complex UI element can have many widgets to implement its complex
|
||||
interface, but group them together as children of a parent and expose only the
|
||||
parent. This is an essential tool to build complex interfaces out of smaller
|
||||
components.
|
||||
|
||||
When a widget is created, for instance with `jwidget_create()` or
|
||||
`jlabel_create()`, its parent is specified as the last parameter.
|
||||
|
||||
In the tree one of the widgets is the ancestor of all the others, and is called
|
||||
the *root* of the scene. In JustUI the root of the tree is normally a `jscene`,
|
||||
because that is how event handling and keyboard input are managed.
|
||||
|
||||
## Managing ownership
|
||||
|
||||
Every widget *owns* its children with regards to memory allocation. This means
|
||||
that when a widget is destroyed, its children are destroyed along with it.
|
||||
Thus, even though the user program performs a function call for each widget to
|
||||
allocate in a scene, a single call freeing the root will destroy all of them.
|
||||
|
||||
Whenever children are moved around, they change ownership. This is important
|
||||
because we always want the parent to outlive the children. Specifically, if a
|
||||
source widget holds a pointer of reference to a target widget, then the source
|
||||
should make sure that either the target is one of its parents, or it is
|
||||
informed when the target is destroyed.
|
87
doc/layout.md
Normal file
87
doc/layout.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# JustUI: Space distribution and layout
|
||||
|
||||
Layout is the process through which space is allocated to widgets, which are
|
||||
then sized and positioned.
|
||||
|
||||
## Content widgets and containers
|
||||
|
||||
Widgets in a scene usually fulfill one of two roles:
|
||||
|
||||
* **Content widgets** such as labels, input fields, or menu browsers, display
|
||||
the interface and receive events. They often have no children, and do their
|
||||
own rendering.
|
||||
* **Containers** such as rows and columns, stacks, or grids, organize other
|
||||
widgets so they align nicely and don't overlap. The widgets they organize are
|
||||
their children; they themselves often perform no rendering.
|
||||
|
||||
JustUI does not enforce this separation, and a single widget can both handle
|
||||
contents and organize children. But the features are usually designed with one
|
||||
of the two roles in mind.
|
||||
|
||||
## Layouts
|
||||
|
||||
Layouts are parameters that can be attached to widgets to automatically size
|
||||
and position their widgets in a useful fashion. This is mostly designed for
|
||||
containers. There are currently 4 types of layouts:
|
||||
|
||||
* **Horizontal boxes** and **vertical boxes** arrange children in a row or a
|
||||
column, respectively. Each widget gets its desired size; if there is space
|
||||
left, widgets can expand according to stretch parameters (more on that
|
||||
later).
|
||||
* **Stacks** arrange all the widgets on top of each other. Only one widget is
|
||||
visible at a time. This is useful for tabbed interfaces.
|
||||
* **Grids** arrange all widgets in a grid. (TODO: WIP)
|
||||
|
||||
A widget that does not have a layout needs to manually determine its own
|
||||
desired size as well as the position its children.
|
||||
|
||||
## The layout process
|
||||
|
||||
The layout process has two phases.
|
||||
|
||||
1. **Size estimation.** In the first phase, each widget declares a desired
|
||||
size. This size often depends on the size of the children, so this phase
|
||||
proceeds bottom-up: first the children declare their desired sizes, then the
|
||||
parents deduce their own desired sizes, and so on.
|
||||
|
||||
2. **Space distribution.** In the second phase, space is distributed by the
|
||||
parents to the children. If the parents have more available space than the
|
||||
children request, extra space is distributed as well. This phase is
|
||||
top-down: first the root distributes the available space to its children,
|
||||
then these children split their shares between their own children, etc.
|
||||
|
||||
All of this proceeds automatically unless some widgets cannot provide a natural
|
||||
size (or only a useless one), in which case the user should give a hint.
|
||||
|
||||
## Internals
|
||||
|
||||
During the first phase, the content size of each widget is evaluated by either
|
||||
the layout's `csize()` function, or the polymorphic widget's `csize()`
|
||||
override. Then `jwidget_msize()` adds in the geometry and stores the margin-box
|
||||
size in the `w` and `h` attributes.
|
||||
|
||||
During the second phase, `jwidget_layout_apply()` distributes the space by
|
||||
dispatching to the layout's `apply()` function, or the polymorphic widget's
|
||||
`apply()` override. It proceeds recursively in a depth-first, prefix order.
|
||||
|
||||
## Layout control
|
||||
|
||||
The widget type provides a natural content size, but the user has the final say
|
||||
in the size of any widget. Any widget can have a minimum and maximum size
|
||||
specified, and every layout guarantees that the allocated size falls in this
|
||||
range (note that limits are examined during the second phase, and the natural
|
||||
content size does not need to be in the acceptable range).
|
||||
|
||||
Additionally, the user can provide stretch rates indicating whether the widget
|
||||
can use more space horizontally and vertically. When there is space to
|
||||
distribute and several widgets are competing to use it, space is allocated in
|
||||
proportion to stretch rates. For instance, a widget with double the stretch
|
||||
rate of its competitors will get twice as much space as them.
|
||||
|
||||
In certain occasions, one might want to disregard the natural content size
|
||||
entirely and distribute space based only on stretch rates, for instance to
|
||||
split a screen in evenly-sized columns even when the contents of the columns
|
||||
have different natural sizes. In this case, one can set the columns to a fixed
|
||||
width of 0 while enabling stretching-beyond-limits. Stretching beyond limits
|
||||
will allow the widgets to grow despite being of fixed size, and because they
|
||||
all start with the same width of 0, they will all end up with the same size.
|
3
doc/scene.md
Normal file
3
doc/scene.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# JustUI: Scenes and events
|
||||
|
||||
TODO.
|
163
doc/widgets.md
Normal file
163
doc/widgets.md
Normal file
|
@ -0,0 +1,163 @@
|
|||
# JustUI: Widget types
|
||||
|
||||
## General widgets and void types
|
||||
|
||||
JustUI defines a base structure `jwidget` useful for containers, as well as a
|
||||
number of derived structures for content widgets. Because C does not support
|
||||
any type of polymorphism, JustUI uses `void *` pointers when referring to "any
|
||||
widget", which accepts pointers to any base or derived structure.
|
||||
|
||||
This relaxed typing scheme mostly applies to the `jwidget_*()` functions that
|
||||
operate on the widget level. Widget-specific functions such as `jinput_value()`
|
||||
request exactly their type of widget as input (in this case, `jinput`).
|
||||
Essentially, this means that `jwidget` can be inherited from transparently,
|
||||
but the other types cannot (a structure attribute must be used). This is a
|
||||
compromise to work with polymorphic types in the C type system.
|
||||
|
||||
## Widget collection
|
||||
|
||||
**`jwidget`** is the base type. It provides children/parent management (see
|
||||
[Widget hierarchy](hierarchy.md)), built-in layouts (see [Space distribution
|
||||
and layout](layout.md)) and size constraints, geometry (margin, border,
|
||||
padding, background), and automatic repaint/relayout mechanisms.
|
||||
|
||||
**`jlabel`** is a content widget that shows a single-line or multi-line piece
|
||||
of text. It supports various text alignment options (left/right/center), line
|
||||
wrapping (letter and word level), and a couple of graphical options.
|
||||
|
||||
**`jscene`** is a special widget designed to be at the root of the widget tree.
|
||||
It provides event dispatching, automatic repaint and layout, keyboard input,
|
||||
and a generic input loop that is suitable for most GUI programs. See [Scenes
|
||||
and events](scene.md).
|
||||
|
||||
**`jinput`** is a one-line input field. It supports direct key input, delayed
|
||||
and instant SHIFT and ALPHA modifiers, as well as modifier locking, with visual
|
||||
hints.
|
||||
|
||||
**`jpainted`** is a very simple wigdet that paints itself with a user-provided
|
||||
function. It is intended to make custom widgets with very little effort. A
|
||||
`jpainted` can be positioned, sized and managed by the widget tree while the
|
||||
user only provides a drawing function.
|
||||
|
||||
**`jfkeys`** represents a function key bar, normally at the bottom of the
|
||||
screen. On fx-9860G, it uses an image to show keys; on fx-CG 50, it supports a
|
||||
string specification that looks like `"@JUMP;;#ROM;#RAM;#ILRAM;#ADDIN"`. It can
|
||||
change options dynamically.
|
||||
|
||||
## Custom widgets and polymorphic operations
|
||||
|
||||
For custom widgets that just have custom rendering a no event management, one
|
||||
can simply use a `jpainted` instance with well-chosen options. However, for
|
||||
reusable widgets that have internal state or event handling, a new widget type
|
||||
should be created.
|
||||
|
||||
A custom widget must be a structure that starts with a `jwidget`. The type
|
||||
itself should be register with `j_register_widget()`, and provide a couple of
|
||||
polymorphic functions. Here is an example with a very trivial widget that holds
|
||||
an integer counter.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
int counter;
|
||||
} jcounter;
|
||||
```
|
||||
|
||||
To be registered in JustUI, a custom widget type must provide a couple of
|
||||
functions, in the form of a `jwidget_poly` structure. All of the following
|
||||
functions are detailed in `<justui/widget-api.h>`, but here is a brief review
|
||||
of the requirements (the `type_poly_` prefix is customary). All of the
|
||||
functions receive pointers to `jcounter` structures, but the type is `void *`
|
||||
because of the limitations mentioned earlier.
|
||||
|
||||
```c
|
||||
/* Receives a (jcounter *). Should set its (w) and (h) attributes to the
|
||||
natural dimensions of the widget. Cannot be NULL. */
|
||||
void jcounter_poly_csize(void *counter);
|
||||
|
||||
/* Paint a jcounter at (x,y). */
|
||||
void jcounter_poly_render(void *counter, int x, int y);
|
||||
|
||||
/* Destroy the resources of a jcounter. (If there are not malloc'd pointers in
|
||||
the structure, this is generally not needed.) */
|
||||
void jcounter_poly_destroy(void *counter);
|
||||
```
|
||||
|
||||
The following functions should be implemented if the custom widget needs to
|
||||
receive events (such as keyboard input). Events are defined in
|
||||
`<justui/jevent.h>`.
|
||||
|
||||
```c
|
||||
/* Handle event (e) sent to a jcounter. Should return true if the event was
|
||||
accepted/used, false if it was ignored/unused. */
|
||||
bool jcounter_poly_event(void *counter, jevent e);
|
||||
```
|
||||
|
||||
The following function is used if (1) an instance of the custom widget has
|
||||
children, and (2) this instance does not have a layout. This is rarely needed.
|
||||
See [Space distribution and layout](layout.md)
|
||||
|
||||
```c
|
||||
/* Receives a (jcounter *) that has its full size set in the (w) and (h)
|
||||
attributes. Should determine the position and size of the children. */
|
||||
void jcounter_poly_layout(void *counter);
|
||||
```
|
||||
|
||||
In general, most of the work consists in specifying the `csize()` and
|
||||
`render()` functions. `destroy()` just has to release the resources allocated
|
||||
during widget creation, `event()` is straightforward, and `layout()` is very
|
||||
rarely needed at all.
|
||||
|
||||
Once the required functions are implemented, a polymorphic widget structure can
|
||||
be defined and registered as a new type. A good way to do this is to register
|
||||
the widget in a constructor located in the same file as the widget creation
|
||||
function, so that it runs automatically if the widget is used.
|
||||
|
||||
```c
|
||||
static jwidget_poly type_jcounter = {
|
||||
.name = "jcounter",
|
||||
.csize = jcounter_poly_csize,
|
||||
.layout = NULL,
|
||||
.render = jcounter_poly_render,
|
||||
.event = NULL,
|
||||
.destroy = NULL,
|
||||
};
|
||||
|
||||
static int jcounter_type_id;
|
||||
|
||||
__attribute__((constructor(2001)))
|
||||
static void j_register_jcounter(void)
|
||||
{
|
||||
jcounter_type_id = j_register_widget(&type_jcounter, "jwidget");
|
||||
}
|
||||
```
|
||||
|
||||
The second parameter to `j_register_widget()` specifies inheritance. `jcounter`
|
||||
inherits from `jwidget`, which means that the unspecified polymorphic functions
|
||||
(`layout`, `event` and `destroy`) will use the default behavior of `jwidget`.
|
||||
This is mostly useful if you don't specify `csize` (the default behavior is to
|
||||
select the smallest size where all children fit) or `render` (the default
|
||||
behavior is to render all visible children).
|
||||
|
||||
The type ID returned by `j_register_widget()` is how JustUI differentiates
|
||||
labels from input fields from custom counters. When creating the widget, you
|
||||
should initialize the `jwidget` field with `jwidget_init()` and specify the
|
||||
type ID. Note how the parent is also passed at creation time so that the new
|
||||
widget can automatically be attached to the tree.
|
||||
|
||||
```c
|
||||
jcounter *jcounter_create(int initial_value, void *parent)
|
||||
{
|
||||
if(jcounter_type_id < 0) return NULL;
|
||||
|
||||
jcounter *c = malloc(sizeof *c);
|
||||
jwidget_init(&c->widget, jcounter_type_id, parent);
|
||||
|
||||
c->counter = initial_value;
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
That's pretty much it. There is a fair amount of boilerplate, but this part
|
||||
is the same for every widget. See `jpainted` for a simple example and `jinput`
|
||||
for a full-featured one.
|
25
giteapc.make
Normal file
25
giteapc.make
Normal file
|
@ -0,0 +1,25 @@
|
|||
# giteapc: version=1 depends=Lephenixnoir/gint
|
||||
|
||||
-include giteapc-config.make
|
||||
|
||||
configure:
|
||||
@ fxsdk build-fx -c $(JUSTUI_CMAKEOPTS_FX)
|
||||
@ fxsdk build-cg -c $(JUSTUI_CMAKEOPTS_CG)
|
||||
|
||||
build:
|
||||
@ fxsdk build-fx
|
||||
@ fxsdk build-cg
|
||||
|
||||
install:
|
||||
@ fxsdk build-fx install
|
||||
@ fxsdk build-cg install
|
||||
|
||||
uninstall:
|
||||
@ if [ -e build-fx/install_manifest.txt ]; then \
|
||||
xargs rm -f < build-fx/install_manifest.txt; \
|
||||
fi
|
||||
@ if [ -e build-cg/install_manifest.txt ]; then \
|
||||
xargs rm -f < build-cg/install_manifest.txt; \
|
||||
fi
|
||||
|
||||
.PHONY: configure build install uninstall
|
10
include/justui/config.h.in
Normal file
10
include/justui/config.h.in
Normal file
|
@ -0,0 +1,10 @@
|
|||
//---
|
||||
// Just UI: Compile-time configuration
|
||||
//---
|
||||
|
||||
#ifndef _JUSTUI_CONFIG
|
||||
#define _JUSTUI_CONFIG
|
||||
|
||||
#define J_VERSION "@JustUI_VERSION@"
|
||||
|
||||
#endif /* _JUSTUI_CONFIG */
|
62
include/justui/defs.h
Normal file
62
include/justui/defs.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
//---
|
||||
// JustUI.defs: Type and utility definitions
|
||||
//---
|
||||
|
||||
#ifndef _J_DEFS
|
||||
#define _J_DEFS
|
||||
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/defs/util.h>
|
||||
#include <gint/defs/attributes.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* j_dirs_t: Quadruplet with four directions */
|
||||
typedef struct {
|
||||
uint8_t top;
|
||||
uint8_t right;
|
||||
uint8_t bottom;
|
||||
uint8_t left;
|
||||
} jdirs;
|
||||
|
||||
/* j_align_t: Alignment options with both horizontal and vertical names */
|
||||
typedef enum {
|
||||
/* Horizontal names */
|
||||
J_ALIGN_LEFT = 0,
|
||||
J_ALIGN_CENTER = 1,
|
||||
J_ALIGN_RIGHT = 2,
|
||||
/* Vertical names */
|
||||
J_ALIGN_TOP = 0,
|
||||
J_ALIGN_MIDDLE = 1,
|
||||
J_ALIGN_BOTTOM = 2,
|
||||
|
||||
} __attribute__((packed)) jalign;
|
||||
|
||||
/* j_arg_t: Standard 4-byte argument types for callbacks */
|
||||
typedef union {
|
||||
/* Pointers of different qualifiers */
|
||||
void *p;
|
||||
void const *cp;
|
||||
volatile void *vp;
|
||||
volatile void const *vcp;
|
||||
/* Integers */
|
||||
int32_t i32;
|
||||
uint32_t u32;
|
||||
|
||||
} GTRANSPARENT j_arg_t;
|
||||
|
||||
/* Vararg macro to cast (void *) parameters to (jwidget *), useful in generic
|
||||
widget functions. J_CAST(x, y, ...) will expand to
|
||||
jwidget *x = x0;
|
||||
jwidget *y = y0;
|
||||
...
|
||||
and accepts from 1 to 4 parameters. */
|
||||
#define J_CAST0(x) _Pragma("GCC error \"J_CAST takes only up to 4 arguments\"")
|
||||
#define J_CAST1(x, ...) jwidget *x = x ## 0; __VA_OPT__(J_CAST0(__VA_ARGS__))
|
||||
#define J_CAST2(x, ...) jwidget *x = x ## 0; __VA_OPT__(J_CAST1(__VA_ARGS__))
|
||||
#define J_CAST3(x, ...) jwidget *x = x ## 0; __VA_OPT__(J_CAST2(__VA_ARGS__))
|
||||
#define J_CAST(x, ...) jwidget *x = x ## 0; __VA_OPT__(J_CAST3(__VA_ARGS__))
|
||||
|
||||
#endif /* _J_DEFS */
|
50
include/justui/jevent.h
Normal file
50
include/justui/jevent.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//---
|
||||
// JustUI.jevent: GUI union event
|
||||
//---
|
||||
|
||||
#ifndef _J_EVENT
|
||||
#define _J_EVENT
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
|
||||
#include <gint/keyboard.h>
|
||||
|
||||
/* jevent: GUI event
|
||||
|
||||
This type is mostly a union type that provides details on every event that
|
||||
occurs in the GUI. These are mostly widget signaling state changes,
|
||||
validations, and other GUI specifics that might require attention. Events
|
||||
can either be reported to the user by the scene (upwards event) or notify
|
||||
widgets of something occuring to them (downwards event).
|
||||
|
||||
JustUI tries hard to not invert flow control and leave the user to decide
|
||||
when to produce downwards events. In a normal situation, events from
|
||||
getkey() are passed to the scene using jscene_process_key() while reading
|
||||
GUI events moving upwards with jscene_pollevent(). This way, the user can
|
||||
decide to filter their key events or even craft some.
|
||||
|
||||
For the sake of convenience, a single function jscene_run() is provided that
|
||||
implements a common form of main loop, which forwards keyboard events to the
|
||||
scene and reports upwards GUI events and ignored key events.
|
||||
|
||||
Event IDs can be registered with j_register_event() and (usually) exposed as
|
||||
global variables. Extensions are meaningful for custom widget types that
|
||||
need their own upwards events. */
|
||||
typedef struct {
|
||||
/* Widget that emitted the event (if upwards), NULL otherwise */
|
||||
void *source;
|
||||
/* Type of event */
|
||||
uint16_t type;
|
||||
/* Reserved for future use */
|
||||
uint16_t _;
|
||||
|
||||
/* Event details or data */
|
||||
union {
|
||||
/* Downward JWIDGET_KEY event or upwards JSCENE_KEY */
|
||||
key_event_t key;
|
||||
};
|
||||
|
||||
} jevent;
|
||||
|
||||
#endif /* _J_EVENT */
|
104
include/justui/jfkeys.h
Normal file
104
include/justui/jfkeys.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
//---
|
||||
// JustUI.jfkeys: Row of function keys
|
||||
//---
|
||||
|
||||
#ifndef _J_JFKEYS
|
||||
#define _J_JFKEYS
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
|
||||
/* jfkeys: Functions keys indicating functions for the F1..F6 keys
|
||||
|
||||
This widget is the typical function key bar with a slightly different
|
||||
design. There are four types of keys, with conventional guidelines:
|
||||
|
||||
* MENU KEYS are used for functions that open menus or navigate between tabs
|
||||
on a same application. The name comes from their primary usage in the
|
||||
system apps. Navigation functions should be easilty reversible and fairly
|
||||
failproof. Menu keys are black rectangular keys with a chipped corner.
|
||||
|
||||
* ENTRY KEYS are used for catalog entries such as the leaves of PRGM's many
|
||||
nested input menus. They represent entries to be chosen from. Entry keys
|
||||
are black rectangular keys.
|
||||
|
||||
* ACTION KEYS are used for generic safe and unsafe actions. Action keys are
|
||||
black round keys.
|
||||
|
||||
* SPECIAL KEYS are used for special functions, such as scrolling to the next
|
||||
set of functions keys when there are several pages, important functions
|
||||
that should catch attention, or particulary unsafe actions. They are round
|
||||
white keys.
|
||||
|
||||
On fx-CG 50, the keys are drawn dynamically using gint's default font, and
|
||||
specified using 6 strings that give the type and name of the keys:
|
||||
|
||||
* "/NAME" for a menu key;
|
||||
* ".NAME" for an entry key;
|
||||
* "@NAME" for an action key;
|
||||
" "#NAME" for a special key.
|
||||
|
||||
The names are separated by semicolons, eg "/F1;;/F3;.F4;@F5;#F6". Several
|
||||
sets of function keys can be defined if separated by a '|' character. For
|
||||
instance, "/F1;#F2|/F1" represents a function bar where the F2
|
||||
function can be hidden by swithing from level 0 to level 1.
|
||||
|
||||
On fx-9860G, there is not enough space to generate keys on-the-fly, so the
|
||||
full specification is just an image. The convention for the image is to be
|
||||
128x8 pixels, with each function key (i) positioned at (x = 21*i + 2) of
|
||||
width 19. Several levels can be stacked up (n levels in an image of height
|
||||
9n-1) and selected independently. */
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
int8_t level;
|
||||
|
||||
#ifdef FX9860G
|
||||
bopti_image_t const *img;
|
||||
#endif
|
||||
|
||||
#ifdef FXCG50
|
||||
char const *labels;
|
||||
#endif
|
||||
|
||||
} jfkeys;
|
||||
|
||||
/* jfkeys_create(): Create a set of function keysed
|
||||
|
||||
The arguments are obviously different on fx-9860G and fx-CG 50. If your
|
||||
application supports both, you might want to specify arguments for both
|
||||
platforms in a single call with jfkeys_create2() which will filter them out
|
||||
for you. Referencing an image unavailable on fx-CG 50 in jfkeys_create2() is
|
||||
safe since the preprocessor will remove that text. */
|
||||
|
||||
#ifdef FX9860G
|
||||
jfkeys *jfkeys_create(bopti_image_t const *img, void *parent);
|
||||
#define jfkeys_create2(img, labels, parent) jfkeys_create(img, parent)
|
||||
#endif
|
||||
|
||||
#ifdef FXCG50
|
||||
jfkeys *jfkeys_create(char const *labels, void *parent);
|
||||
#define jfkeys_create2(img, labels, parent) jfkeys_create(labels, parent)
|
||||
#endif
|
||||
|
||||
/* jfkeys_set(): Replace functions
|
||||
This will also reset the level to 0. */
|
||||
|
||||
#ifdef FX9860G
|
||||
void jfkeys_set(jfkeys *keys, bopti_image_t const *img);
|
||||
#define jfkeys_set2(keys, img, labels) jfkeys_set(keys, img)
|
||||
#endif
|
||||
|
||||
#ifdef FXCG50
|
||||
void jfkeys_set(jfkeys *keys, char const *labels);
|
||||
#define jfkeys_set2(keys, img, labels) jfkeys_set(keys, labels)
|
||||
#endif
|
||||
|
||||
/* jfkeys_level(): Return the current function key level */
|
||||
int jfkeys_level(jfkeys *keys);
|
||||
|
||||
/* jfkeys_set_level(): Set the function key level */
|
||||
void jfkeys_set_level(jfkeys *keys, int level);
|
||||
|
||||
#endif /* _J_JFKEYS */
|
91
include/justui/jinput.h
Normal file
91
include/justui/jinput.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
//---
|
||||
// JustUI.jinput: One-line input field
|
||||
//---
|
||||
|
||||
#ifndef _J_JINPUT
|
||||
#define _J_JINPUT
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/p/vec.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
|
||||
/* jinput: One-line input field
|
||||
|
||||
This widget is used to read input from the user. It has a single line of
|
||||
text which can be edited when focused, and an optional prompt. On the right,
|
||||
an indicator displays the status of modifier keys.
|
||||
|
||||
The edition rules support both the OS' native one-key-at-time input system,
|
||||
and the usual computer modifer-keys-held method.
|
||||
|
||||
* The normal insertion mode is used by default.
|
||||
* When pressing SHIFT or ALPHA in combination with a key (without releasing
|
||||
SHIFT or ALPHA, as on a computer), the secondary or alphabetic function of
|
||||
the key is used.
|
||||
* Pressing then releasing SHIFT or ALPHA activates the secondary or
|
||||
alphabetic function for the next key press.
|
||||
* Double-tapping SHIFT or ALPHA locks the corresponding mode on until the
|
||||
locked mode is disabled by another press-release of the same modifier key.
|
||||
|
||||
TODO: jinput: Selection with SHIFT
|
||||
TODO: jscene: Clipboard support
|
||||
|
||||
A timer is used to make the cursor blink, sending JSCENE_REPAINT events
|
||||
every second or so. The timer is held only during editing and freed when
|
||||
input stops. If no timer is available the cursor is simply not animated for
|
||||
the corresponding input sequence.
|
||||
|
||||
Events:
|
||||
* JINPUT_VALIDATED when EXE is pressed during edition
|
||||
* JINPUT_CANCELED when EXIT is pressed during edition */
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
|
||||
/* Color and font of text */
|
||||
int color;
|
||||
font_t const *font;
|
||||
|
||||
/* Optional prompt */
|
||||
char const *prompt;
|
||||
|
||||
/* Text being input */
|
||||
char *text;
|
||||
/* Current size (including NUL) and maximum size */
|
||||
uint16_t size;
|
||||
uint16_t max;
|
||||
|
||||
/* Current cursor position */
|
||||
int16_t cursor;
|
||||
/* Current input mode */
|
||||
uint8_t mode;
|
||||
/* Timer ID for the cursor state */
|
||||
int8_t timer;
|
||||
|
||||
} jinput;
|
||||
|
||||
/* Type IDs */
|
||||
extern uint16_t JINPUT_VALIDATED;
|
||||
extern uint16_t JINPUT_CANCELED;
|
||||
|
||||
/* jinput_create(): Create an input field
|
||||
|
||||
The input field is disabled until it receives focus from its scene. The
|
||||
edited text is initially empty and is allocated when needed. The length
|
||||
specifies the maximum amount of bytes in the input. */
|
||||
jinput *jinput_create(char const *prompt, size_t length, void *parent);
|
||||
|
||||
/* Trivial properties */
|
||||
void jinput_set_text_color(jinput *input, int color);
|
||||
void jinput_set_font(jinput *input, font_t const *font);
|
||||
void jinput_set_prompt(jinput *input, char const *prompt);
|
||||
|
||||
/* Current value visible in the widget, normally useful upon receiving the
|
||||
JINPUT_VALIDATED event, not guaranteed otherwise */
|
||||
char const *jinput_value(jinput *input);
|
||||
|
||||
/* jinput_clear(): Clear current text */
|
||||
void jinput_clear(jinput *input);
|
||||
|
||||
#endif /* _J_JINPUT */
|
134
include/justui/jlabel.h
Normal file
134
include/justui/jlabel.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
//---
|
||||
// JustUI.jlabel: Simple one-line or multi-line text label without formatting
|
||||
//---
|
||||
|
||||
#ifndef _J_JLABEL
|
||||
#define _J_JLABEL
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/p/vec.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
|
||||
/* jlabel_wrapmode: Text wrapping options */
|
||||
typedef enum jwrapmode {
|
||||
/* Wrap only at \n characters */
|
||||
J_WRAP_NONE,
|
||||
/* Break only at spaces; if a word is longer than a full line, breaking in
|
||||
that word is allowed */
|
||||
J_WRAP_WORD,
|
||||
/* Break only at spaces and tabs, even if a word is longer than a line */
|
||||
J_WRAP_WORD_ONLY,
|
||||
/* Break at any letter */
|
||||
J_WRAP_LETTER,
|
||||
|
||||
} __attribute__((packed)) jwrapmode;
|
||||
|
||||
/* jlabel: One-line or multi-line piece of text, without formatting.
|
||||
|
||||
This widget is used for mundane text printing. All the text in the label is
|
||||
printed with a single font. The text can be aligned horizontally and
|
||||
vertically within the widget ("block alignment"), and the lines can also be
|
||||
aligned horizontally within the text block ("text alignment").
|
||||
|
||||
+-------------+
|
||||
| label | Text is centered within the widget (block centered).
|
||||
| 1 | Lines are aligned by their center (text centered).
|
||||
+-------------+
|
||||
|
||||
+-------------+
|
||||
| label | Text is centered within the widget (block centered).
|
||||
| 2 | Lines are aligned by their left side (text left).
|
||||
+-------------+
|
||||
|
||||
+-------------+
|
||||
|label | Text is aligned left within the widget (block left).
|
||||
| 3 | Lines are aligned by their center (text centered).
|
||||
+-------------+
|
||||
|
||||
Lines are broken at '\n' characters. Depending on the word wrap settings,
|
||||
lines can also be broken whenever the edge of the widget is reached. Extra
|
||||
line spacing (even negative) can be specified.
|
||||
|
||||
The natural size of the widget is always computed based on newline
|
||||
characters, since there is no way to wrap until a width has been assigned.
|
||||
If you want predictable results, use size constraints.
|
||||
|
||||
The text color and font can also be set, using the same color values and
|
||||
fonts as <gint/display.h>. If the font is set to NULL, gint's default font
|
||||
is used. The background color for the whole widget is handled by jwidget. */
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
|
||||
/* Horizontal block alignment */
|
||||
jalign block_halign;
|
||||
/* Vertical block alignment */
|
||||
jalign block_valign;
|
||||
/* Text alignment */
|
||||
jalign text_align;
|
||||
/* Pixels of spacing between each line, in addition to font->height */
|
||||
int8_t line_spacing;
|
||||
|
||||
/* Text to display */
|
||||
char const *text;
|
||||
/* List of line break offsets; indexes 2n and 2n+1 give the start and end
|
||||
of line number n */
|
||||
DECLARE_VEC(uint16_t, breaks);
|
||||
|
||||
/* Text wrapping mode */
|
||||
enum jwrapmode wrap_mode;
|
||||
/* Whether the text has been allocated by the label or supplied by user */
|
||||
int8_t owns_text;
|
||||
/* Block width (maximum length of a rendered line) */
|
||||
uint16_t block_width;
|
||||
|
||||
/* Color and font of text; if NULL, gint's default font is used */
|
||||
int color;
|
||||
font_t const *font;
|
||||
|
||||
} jlabel;
|
||||
|
||||
/* jlabel_create(): Create a label with a fixed text
|
||||
|
||||
Initially the label has the supplied string as text (which must outlive the
|
||||
widget). If you want dynamic text, you can provide an empty string and use
|
||||
jlabel_printf() later. */
|
||||
jlabel *jlabel_create(char const *text, void *parent);
|
||||
|
||||
/* jlabel_set_text(): Set a fixed string
|
||||
This function sets the label text to a fixed string. This is the same as in
|
||||
jlabel_create(). The string is not copied, it must outlive the label. This
|
||||
is the most useful when the provided string is a literal. */
|
||||
void jlabel_set_text(jlabel *l, char const *text);
|
||||
|
||||
/* jlabel_asprintf(): Generate and set a formatted string
|
||||
This function generates the label string with printf-formatting. The
|
||||
resulting string is created with malloc() and owned by the label; it is
|
||||
destroyed when the text is replaced of the label is destroyed. Because of
|
||||
how asprintf() works, the string is generated twice. Returns the number of
|
||||
characters printed. */
|
||||
int jlabel_asprintf(jlabel *l, char const *format, ...);
|
||||
|
||||
/* jlabel_snprintf(): Generate and set a formatted string
|
||||
Similar to jlabel_asprintf(), but an upper bound on the length of the result
|
||||
string has to be provided. This avoids generating the string twice. Return
|
||||
the number of characters printed. */
|
||||
int jlabel_snprintf(jlabel *l, size_t size, char const *format, ...);
|
||||
|
||||
/* jlabel_text(): Get the current string */
|
||||
char const *jlabel_text(jlabel *l);
|
||||
|
||||
/* Set block and text alignment, individually */
|
||||
void jlabel_set_block_alignment(jlabel *l, jalign horz, jalign vert);
|
||||
void jlabel_set_text_alignment(jlabel *l, jalign align);
|
||||
/* Set both horizontal alignments at the same time (most natural) */
|
||||
void jlabel_set_alignment(jlabel *l, jalign horizontal_align);
|
||||
|
||||
/* Trivial properties */
|
||||
void jlabel_set_line_spacing(jlabel *l, int line_spacing);
|
||||
void jlabel_set_wrap_mode(jlabel *l, jwrapmode mode);
|
||||
void jlabel_set_text_color(jlabel *l, int color);
|
||||
void jlabel_set_font(jlabel *l, font_t const *font);
|
||||
|
||||
#endif /* _J_JLABEL */
|
118
include/justui/jlayout.h
Normal file
118
include/justui/jlayout.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
//---
|
||||
// JustUI.jlayout: Widget positioning mechanisms
|
||||
//---
|
||||
|
||||
#ifndef _J_JLAYOUT
|
||||
#define _J_JLAYOUT
|
||||
|
||||
#include <justui/defs.h>
|
||||
|
||||
/* jlayout_type: Built-in layout mechanisms */
|
||||
typedef enum {
|
||||
/* User or sub-class places children manually */
|
||||
J_LAYOUT_NONE = 0,
|
||||
/* Children are laid out in a vertical box; spacing applies. Extra
|
||||
space is redistributed proportionally to the stretch attribute. */
|
||||
J_LAYOUT_VBOX,
|
||||
/* Same as J_LAYOUT_VBOX, but horizontally. */
|
||||
J_LAYOUT_HBOX,
|
||||
/* Children are stacked, only one is visible at a time. */
|
||||
J_LAYOUT_STACK,
|
||||
/* Children are laid out in a grid; spacing applies. */
|
||||
J_LAYOUT_GRID,
|
||||
|
||||
} jlayout_type;
|
||||
|
||||
//---
|
||||
// Manual layout
|
||||
//---
|
||||
|
||||
/* jlayout_set_manual(): Remove a layout from a widget
|
||||
This function removes the current layout of the widget. */
|
||||
void jlayout_set_manual(void *w);
|
||||
|
||||
//---
|
||||
// Box layout
|
||||
//---
|
||||
|
||||
/* jlayout_box: Parameters for VBOX and HBOX layouts */
|
||||
typedef struct {
|
||||
/* Spacing between elements */
|
||||
uint8_t spacing;
|
||||
uint :24;
|
||||
|
||||
} jlayout_box;
|
||||
|
||||
/* jlayout_get_hbox(), jlayout_get_vbox(): Get configuration for box layouts
|
||||
These functions return the jlayout_box parameters for widgets that have a
|
||||
box layout, and NULL otherwise. */
|
||||
jlayout_box *jlayout_get_hbox(void *w);
|
||||
jlayout_box *jlayout_get_vbox(void *w);
|
||||
|
||||
/* jlayout_set_hbox(), jlayout_set_vbox(): Create box layouts
|
||||
These functions configure the specified widget to have a box layout, and
|
||||
return the jlayout_box parameters to configure that layout. The parameters
|
||||
are initialized even if the widget previously had a box layout. */
|
||||
jlayout_box *jlayout_set_hbox(void *w);
|
||||
jlayout_box *jlayout_set_vbox(void *w);
|
||||
|
||||
//---
|
||||
// Stack layouts
|
||||
//---
|
||||
|
||||
/* jlayout_stack: Parameters for STACK layouts */
|
||||
typedef struct {
|
||||
/* Index of currently-visible child */
|
||||
int8_t active;
|
||||
uint :24;
|
||||
|
||||
} jlayout_stack;
|
||||
|
||||
/* jlayout_get_stack(): Get configuration for stack layouts
|
||||
For widgets that have a stack layout, returns a pointer to the parameters.
|
||||
Otherwise, returns NULL. */
|
||||
jlayout_stack *jlayout_get_stack(void *w);
|
||||
|
||||
/* jlayout_set_stack(): Create stack layouts
|
||||
Configure the specified widget to have a stack layout and return the new
|
||||
parameters. The new layout is cleared even if the widget previously had a
|
||||
stack layout. */
|
||||
jlayout_stack *jlayout_set_stack(void *w);
|
||||
|
||||
//---
|
||||
// Grid layouts
|
||||
//---
|
||||
|
||||
typedef enum {
|
||||
/* Rows from top to bottom */
|
||||
J_LAYOUT_GRID_TOPDOWN,
|
||||
/* Rows from bottom to top */
|
||||
J_LAYOUT_GRID_BOTTOMUP,
|
||||
/* Columns from left to right */
|
||||
J_LAYOUT_GRID_LEFTRIGHT,
|
||||
/* Columns from right to left */
|
||||
J_LAYOUT_GRID_RIGHTLEFT,
|
||||
|
||||
} jlayout_grid_order;
|
||||
|
||||
typedef struct {
|
||||
/* Spacing between rows and between columns */
|
||||
uint8_t row_spacing;
|
||||
uint8_t col_spacing;
|
||||
/* Child order. The major order specifies whether the grid is filled by
|
||||
rows or by columns. The minor order specifies how these rows or
|
||||
columns are filled themselves. There must be exactly one vertical
|
||||
setting and one horizontal setting. */
|
||||
uint major_order :2;
|
||||
uint minor_order :2;
|
||||
/* Number of rows, and number of columns; default to -1, in which case
|
||||
the minor size is determined based on the first minor group and the
|
||||
major size is calculated greedily. */
|
||||
uint rows :6;
|
||||
uint cols :6;
|
||||
|
||||
} jlayout_grid;
|
||||
|
||||
/* TODO: Functions for grid layouts */
|
||||
|
||||
#endif /* _J_JLAYOUT */
|
65
include/justui/jpainted.h
Normal file
65
include/justui/jpainted.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//---
|
||||
// JustUI.jpainted: Widget defined by just a size and painting function
|
||||
//---
|
||||
|
||||
#ifndef _J_JPAINTED
|
||||
#define _J_JPAINTED
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
|
||||
/* jpainted: Simple widget designed to integrate low-effort rendering
|
||||
|
||||
This widget is simply a rectangle on which a custom function does the
|
||||
rendering. It allows the GUI to have custom rendering with very little code,
|
||||
as the widget only requires a natural size and a renderer.
|
||||
|
||||
The natural size doesn't need to be exactly the size of the widget; ideally
|
||||
it should be larger than what the rendered needs. In any case the natural
|
||||
size is not the final one, as stretch can be used in layouts.
|
||||
|
||||
This widget will typically be used like this:
|
||||
|
||||
void draw_something(int x, int y) {
|
||||
dimage(x, y, ...);
|
||||
dtext(x+2, y+10, ...);
|
||||
}
|
||||
jpainted *widget = jpainted_create(draw_something, NULL, 10, 10, parent);
|
||||
|
||||
The rendering function can optionally take an argument; this argument can be
|
||||
an integer or a pointer (essentially).
|
||||
|
||||
void draw_value(int x, int y, uint32_t *value) {
|
||||
dprint(x, y, "%08X", *value);
|
||||
}
|
||||
uint32_t my_value;
|
||||
jpainted *widget = jpainted_create(draw_value, &my_value, 30, 8, parent);
|
||||
|
||||
The type of the argument is defined as j_arg_t but you don't need to create
|
||||
a j_arg_t object, just pass the argument directly. If you don't have an
|
||||
argument, pass NULL.
|
||||
|
||||
Note that in this example you will need to set (widget->update = 1) whenever
|
||||
you change (my_value), in order for the scene to produce a PAINT event.
|
||||
Otherwise, a change in (my_value) will not be seen on-screen until the next
|
||||
time the scene is painted.
|
||||
|
||||
The layout and rendering will be performed automatically, allowing the GUI
|
||||
to benefit from automated positioning with virtually no overhead. */
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
/* Renderer function and its argument */
|
||||
void (*paint)(int x, int y, j_arg_t arg);
|
||||
j_arg_t arg;
|
||||
/* Natural size */
|
||||
int16_t natural_w;
|
||||
int16_t natural_h;
|
||||
|
||||
} jpainted;
|
||||
|
||||
/* jpainted_create(): Create a simple painted widget */
|
||||
jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w,
|
||||
int natural_h, void *parent);
|
||||
|
||||
#endif /* _J_JPAINTED */
|
105
include/justui/jscene.h
Normal file
105
include/justui/jscene.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
//---
|
||||
// JustUI.jscene: Root object that provides keyboard focus and event handling
|
||||
//---
|
||||
|
||||
#ifndef _J_JSCENE
|
||||
#define _J_JSCENE
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jevent.h>
|
||||
|
||||
#define JSCENE_QUEUE_SIZE 32
|
||||
|
||||
/* jscene: A widget scene with keyboard focus and event handling
|
||||
|
||||
This widget is designed to be the root of a widget tree. It keeps track of
|
||||
widgets with keyboard focus, feeds them keyboard events, and catches other
|
||||
useful events to store them in an event queue. */
|
||||
typedef struct {
|
||||
jwidget widget;
|
||||
|
||||
/* Location on screen */
|
||||
int16_t x, y;
|
||||
/* Widget with focus */
|
||||
jwidget *focus;
|
||||
|
||||
/* Circular event queue */
|
||||
jevent queue[JSCENE_QUEUE_SIZE];
|
||||
uint8_t queue_first;
|
||||
uint8_t queue_next;
|
||||
|
||||
/* Number of events lots */
|
||||
uint16_t lost_events;
|
||||
|
||||
} jscene;
|
||||
|
||||
/* Events */
|
||||
extern uint16_t JSCENE_NONE;
|
||||
extern uint16_t JSCENE_PAINT;
|
||||
extern uint16_t JSCENE_KEY;
|
||||
|
||||
/* jscene_create(): Create a new scene at that specified screen position */
|
||||
jscene *jscene_create(int x, int y, int w, int h, void *parent);
|
||||
|
||||
/* jscene_create_fullscreen(): Create a fullscreen scene
|
||||
The position is (0,0) and the size is (DWIDTH,DHEIGHT). */
|
||||
jscene *jscene_create_fullscreen(void *parent);
|
||||
|
||||
/* jscene_layout(): Layout a scene
|
||||
This is automatically called by jscene_render(), but may be useful if you
|
||||
need to know the size of your widgets before rendering. The layout is
|
||||
recomputed only if something in the scene has changed. */
|
||||
#define jscene_layout jwidget_layout
|
||||
|
||||
/* jscene_render(): Layout and render a scene
|
||||
Layout is lazy and performed only if needed. The scene is rendered at its
|
||||
(x,y) point. */
|
||||
void jscene_render(jscene *scene);
|
||||
|
||||
//---
|
||||
// Events sent from the scene to the user
|
||||
//---
|
||||
|
||||
/* jscene_read_event(): Get the next upwards event from the queue
|
||||
If there is no event, returns an event of type JSCENE_NONE. */
|
||||
jevent jscene_read_event(jscene *scene);
|
||||
|
||||
/* jscene_queue_event(): Queue an upwards event to be later read by the user
|
||||
This function records an event in the scene's queue, which will later be
|
||||
returned by jscene_pollevent(). This is mostly used by widget code to signal
|
||||
stuff that warrants attention. */
|
||||
void jscene_queue_event(jscene *scene, jevent event);
|
||||
|
||||
//---
|
||||
// Keyboard focus and keyboard events
|
||||
//---
|
||||
|
||||
/* jscene_focused_widget(): Query the widget that currently has focus */
|
||||
void *jscene_focused_widget(jscene *scene);
|
||||
|
||||
/* jscene_set_focused_widget(): Move the focus to a widget
|
||||
The selected widget, obviously, must be a descendant of the scene. */
|
||||
void jscene_set_focused_widget(jscene *scene, void *widget);
|
||||
|
||||
/* jscene_process_key(): Send a downwards key event to the focused widget
|
||||
Returns true if the event was accepted, false if it was ignored. */
|
||||
bool jscene_process_key(jscene *scene, key_event_t event);
|
||||
|
||||
/* jscene_process_event(): Send a downards GUI event to the focused widget */
|
||||
// bool jscene_process_event(jscene *scene, jevent event);
|
||||
|
||||
|
||||
/* jscene_run(): Run a scene's main loop
|
||||
|
||||
This function implements a main control loop that sleeps when there is
|
||||
nothing to do, forwards all key events to the scene, and returns only to
|
||||
notify GUI events or hand over key events that have been ignored by the
|
||||
scene.
|
||||
|
||||
If a scene event occurs, returns it. If a key event occurs, an event of type
|
||||
JSCENE_KEY is return and its .key attribute contains the details of the
|
||||
forwarded keyboard event. */
|
||||
jevent jscene_run(jscene *scene);
|
||||
|
||||
#endif /* _J_JSCENE */
|
158
include/justui/jwidget-api.h
Normal file
158
include/justui/jwidget-api.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
//---
|
||||
// JustUI.jwidget-API: API for subclassed widget types
|
||||
//---
|
||||
|
||||
#ifndef _J_JWIDGET_API
|
||||
#define _J_JWIDGET_API
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jevent.h>
|
||||
#include <gint/keyboard.h>
|
||||
|
||||
//---
|
||||
// Polymorphic operations on widgets
|
||||
//
|
||||
// The following operations are widget-type-dependent. Derived widget types
|
||||
// must provide their implementation through a jwidget_poly structure. The
|
||||
// supplied functions will then be called at different points during the
|
||||
// lifetime of the derived widgets.
|
||||
//---
|
||||
|
||||
/* jwidget_poly_csize_t: Determine the natural content size of a widget
|
||||
|
||||
This function is called during layout. It should set the natural size of the
|
||||
content box of the widget in (w->w) and (w->h). If the widget has a layout,
|
||||
this function will not be called; instead, the layout will determine the
|
||||
natural content size. Thus, this function is mostly useful for content
|
||||
widgets and not useful for containers.
|
||||
|
||||
Implementations of this function should use jwidget_msize() on the children
|
||||
to position their margin box within the widget's content box. The size set
|
||||
by this function needs not be in the minimum/maximum range of the widget. */
|
||||
typedef void jwidget_poly_csize_t(void *w);
|
||||
|
||||
/* jwidget_poly_layout_t: Layout a widget after its size has been set
|
||||
|
||||
This function is called during the second phase of the layout process, if
|
||||
the widget has no layout. The margin-box size allocated to the widget has
|
||||
been set in (w->w) and (w->h); the widget must now position its contents and
|
||||
children. If the widget has a layout, the layout's specialized function is
|
||||
called instead of this one. */
|
||||
typedef void jwidget_poly_layout_t(void *w);
|
||||
|
||||
/* jwidget_poly_render_t: Render a widget
|
||||
|
||||
This function is called during rendering after the widget's geometry is
|
||||
drawn. (x,y) are the coordinates of the content box. This function must
|
||||
render widget-specific visuals; there is no clipping, so the widget should
|
||||
honor its width and height.
|
||||
|
||||
This function should call jwidget_render() for all children that need to be
|
||||
rendered. jwidget_render() handles the geometry and takes as parameters the
|
||||
coordinates of the margin box, so it can be called as:
|
||||
|
||||
jwidget_render(child, x + child->x, y + child->y).
|
||||
|
||||
It will draw the geometry and call the polymorphic renderer of the child at
|
||||
its content-box coordinates. Normally you can ignore geometry altogether. */
|
||||
typedef void jwidget_poly_render_t(void *w, int x, int y);
|
||||
|
||||
/* jwidget_poly_event_t: Handle an event
|
||||
|
||||
This function is called when an event is targeted at a widget, including for
|
||||
key events. This function is somewhat of a catch-all function for dynamic
|
||||
occurrences. The widget should either accept the event, do something with
|
||||
it, and return true, or refuse the event, do nothing and return false. This
|
||||
is influences events that propagate, such as key events. */
|
||||
typedef bool jwidget_poly_event_t(void *w, jevent e);
|
||||
|
||||
/* jwidget_poly_destroy_t: Destroy a widget's specific resources
|
||||
|
||||
This function must destroy the widget-specific resources. It is called by
|
||||
jwidget_destroy(), which follows it by freeing the widget's standard data
|
||||
and destroying the children. This function can be NULL if there are no
|
||||
widget-specific resources to free. */
|
||||
typedef void jwidget_poly_destroy_t(void *w);
|
||||
|
||||
/* jwidget_poly: Polymorphic interface for a widget type */
|
||||
typedef struct {
|
||||
/* Type name, used for display and inheritance */
|
||||
char const *name;
|
||||
/* Polymorphic functions */
|
||||
jwidget_poly_csize_t *csize;
|
||||
jwidget_poly_layout_t *layout;
|
||||
jwidget_poly_render_t *render;
|
||||
jwidget_poly_event_t *event;
|
||||
jwidget_poly_destroy_t *destroy;
|
||||
|
||||
} jwidget_poly;
|
||||
|
||||
//---
|
||||
// Widget registration
|
||||
//---
|
||||
|
||||
/* j_register_widget(): Register a new widget type
|
||||
|
||||
This function returns a new widget type ID to pass to jwidget_init() when
|
||||
creating widgets of the custom type. Returns -1 if registration fails. The
|
||||
polymorphic structure must outlive all widgets of the custom type.
|
||||
|
||||
If (inherits) is non-NULL, the new type will inherit from the widget type
|
||||
with the provided name. All NULL functions in (poly) will be replaced by the
|
||||
parent type's functions. This mechanism only works if widgets are declared
|
||||
in inheritance order, which is normally enforced by constructor priority. */
|
||||
int j_register_widget(jwidget_poly *poly, char const *inherits);
|
||||
|
||||
/* j_register_event(): Register a new event type
|
||||
|
||||
This function returns a new ID to set in the (type) field of jevent objects.
|
||||
The ID is unique, and can be trusted to be always valid (unless you register
|
||||
more than 64k events in which case you asked for the trouble). */
|
||||
int j_register_event(void);
|
||||
|
||||
//---
|
||||
// Helpers to implement custom widget types
|
||||
//---
|
||||
|
||||
/* jwidget_init(): Initialize a widget
|
||||
|
||||
This function should be called in the constructor for the subclassed widget
|
||||
type, preferably as soon as possible. It initializes common widget
|
||||
attributes, sets the widget type, and declares the parent.
|
||||
|
||||
A subclassed widget type must have a jwidget as a first member (so that the
|
||||
address of any instance of the subclassed widget is a valid pointer to
|
||||
jwidget), and that jwidget must be initialized with jwidget_init().
|
||||
|
||||
@w Widget to initialize
|
||||
@type Type ID, as returned by j_register_widget()
|
||||
@parent Parent, same as in jwidget_create() */
|
||||
void jwidget_init(jwidget *w, int type, void *parent);
|
||||
|
||||
/* jwidget_msize(): Compute and apply the natural size of a widget's margin-box
|
||||
|
||||
This function computes the widget's natural margin-box size. It determines
|
||||
the natural content size with the csize() function of either the layout or
|
||||
the widget type, then adds the geometry.
|
||||
|
||||
The margin-box size is stored in the (w) and (h) attributes of the widget.
|
||||
This function should only be called during the first phase of the layout,
|
||||
to implement subclassed csize() functions. Usually, the parent will
|
||||
implement a customn csize() function by combining the position and msize()
|
||||
of its children. */
|
||||
void jwidget_msize(void *w);
|
||||
|
||||
/* jwidget_emit(): Emit an upwards event from this widget
|
||||
|
||||
This function walks up the tree until it finds a jscene that can store the
|
||||
event. If there is no jscene in the tree, the event is ignored. The (source)
|
||||
field can be omitted and will be set to the widget's address by default. */
|
||||
void jwidget_emit(void *w, jevent e);
|
||||
|
||||
/* jwidget_event(): Send a downwards event to a widget
|
||||
|
||||
This function calls the polymorphic event function of the targeted widget to
|
||||
notify it of the specified event. */
|
||||
bool jwidget_event(void *w, jevent e);
|
||||
|
||||
#endif /* _J_JWIDGET_API */
|
370
include/justui/jwidget.h
Normal file
370
include/justui/jwidget.h
Normal file
|
@ -0,0 +1,370 @@
|
|||
//---
|
||||
// JustUI.jwidget: Base object for all widgets
|
||||
//---
|
||||
|
||||
#ifndef _J_JWIDGET
|
||||
#define _J_JWIDGET
|
||||
|
||||
#include <justui/defs.h>
|
||||
#include <justui/jlayout.h>
|
||||
|
||||
/* jwidget: Base object for all widgets.
|
||||
|
||||
Functions that ought to work on any widget take void * parameters; this
|
||||
includes jwidget and derived types whose first attribute is a jwidget. A
|
||||
void * parameter named "w" is implicitly a widget.
|
||||
|
||||
Widgets for a scene are arranged in a tree hierarchy; each object has a
|
||||
unique parent and a number of distinct children, which they own. Widgets
|
||||
destroy their children when they are destroyed. The parent of all the
|
||||
widgets in the scene is called the root, it's the only one without a parent.
|
||||
|
||||
Every widget has an HTML-like box model described by its geometry object.
|
||||
See the jwidget_geometry type below for details. By default widgets have no
|
||||
geometry to save memory, it is only created when actually used.
|
||||
|
||||
The size of each widget can be controlled in two ways. Initially, the
|
||||
widget's contents suggest a "natural size". The user can then restrict
|
||||
acceptable sizes by specifying a size hint along with a policy. See the
|
||||
jwidget_size_policy type below for details. Additionally, the user can set
|
||||
stretch parameters to allow the widget to grow and occupy available space.
|
||||
|
||||
Widgets usually have one of two roles; either they are "containers" for
|
||||
other widgets, or they are "content widgets" that render text and images, or
|
||||
take input from the user. In order to make containers easy to design, each
|
||||
widget can be equipped with a "layout" that automatically arranges children
|
||||
in useful ways, such as vertical lists or grids. Content widgets usually
|
||||
don't have layouts.
|
||||
|
||||
Polymorphic operations are defined for each widget type and accessed through
|
||||
the type attribute. See the widget extension API at the end of this header
|
||||
for details on polymorphic operations.
|
||||
|
||||
The following attributes can be accessed by the user:
|
||||
.parent (read-only)
|
||||
.children[] (read-only)
|
||||
.child_count (read-only) */
|
||||
typedef struct jwidget {
|
||||
/* Parent and children in the widget tree */
|
||||
struct jwidget *parent;
|
||||
struct jwidget **children;
|
||||
|
||||
/* Location within the content-box of the parent (after layout) */
|
||||
int16_t x, y;
|
||||
/* Margin-box size in pixels (after layout) */
|
||||
int16_t w, h;
|
||||
/* Size hints: these are user-provided sizes, which are combined with the
|
||||
size policy to determine acceptable widget dimensions */
|
||||
int16_t min_w, min_h;
|
||||
int16_t max_w, max_h;
|
||||
|
||||
/* Widget geometry, defaults to a fixed geometry object */
|
||||
struct jwidget_geometry *geometry;
|
||||
/* Layout data, access with the jlayout_{get,set}_*() functions) */
|
||||
union {
|
||||
jlayout_box layout_box;
|
||||
jlayout_stack layout_stack;
|
||||
jlayout_grid layout_grid;
|
||||
};
|
||||
|
||||
/* Widget type, used to find polymorphic operations */
|
||||
uint8_t type;
|
||||
/* Number of children */
|
||||
uint8_t child_count;
|
||||
/* Number of pointers allocated in the children array */
|
||||
uint8_t child_alloc;
|
||||
/* Horizontal and vertical stretch rates */
|
||||
uint stretch_x :4;
|
||||
uint stretch_y :4;
|
||||
|
||||
/* Type of layout (see the jlayout_type enum) */
|
||||
uint layout :4;
|
||||
/* Whether stretch can go beyond the maximum size */
|
||||
uint stretch_force :1;
|
||||
/* Whether the layout needs to be recomputed */
|
||||
uint dirty :1;
|
||||
/* Whether the widget needs to be redrawn */
|
||||
uint update :1;
|
||||
/* Whether widget is visible inside its parent */
|
||||
uint visible :1;
|
||||
|
||||
uint :24;
|
||||
|
||||
} jwidget;
|
||||
|
||||
/* jwidget_border_style: Styles of widget borders */
|
||||
typedef enum {
|
||||
/* No border */
|
||||
J_BORDER_NONE,
|
||||
/* Border is a solid color */
|
||||
J_BORDER_SOLID,
|
||||
/* TODO: More border styles (especially on fx-CG 50) */
|
||||
|
||||
} jwidget_border_style;
|
||||
|
||||
/* jwidget_geometry: Built-in positioning and border geometry
|
||||
|
||||
Every widget has a "geometry", which consists of a border and two layers of
|
||||
spacing around the widget:
|
||||
* The "padding" is the spacing between the widget's contents and its border
|
||||
* The "border" is a visible decoration around the widget's contents
|
||||
* The "margin" is the spacing between the border and the widget's edge
|
||||
|
||||
For users familiar with the CSS box model, this is it. Note, however, that
|
||||
unlike the common CSS property (box-sizing: border-box), JustUI counts the
|
||||
margin as widget-owned space and measures widgets using the margin box. */
|
||||
typedef struct jwidget_geometry {
|
||||
/* Padding (in pixels) on all four sides; access either using padding.top,
|
||||
.right, .bottom and .left, or using paddings[0] through paddings[3] */
|
||||
union {
|
||||
uint8_t paddings[4];
|
||||
jdirs padding;
|
||||
};
|
||||
/* Width of the border on all four sides */
|
||||
union {
|
||||
uint8_t borders[4];
|
||||
jdirs border;
|
||||
};
|
||||
/* Size of the margin (in pixel) on all four sides */
|
||||
union {
|
||||
uint8_t margins[4];
|
||||
jdirs margin;
|
||||
};
|
||||
/* Border color (as in <gint/display.h>) */
|
||||
int border_color;
|
||||
/* Border style */
|
||||
jwidget_border_style border_style;
|
||||
/* Background color */
|
||||
int background_color;
|
||||
|
||||
} jwidget_geometry;
|
||||
|
||||
/* Downwards key event: widget is notified of a key press that ocurred while it
|
||||
had active focus.
|
||||
-> .data.key: Key event */
|
||||
extern uint16_t JWIDGET_KEY;
|
||||
/* Downwards focus-in event: the widget has just received focus */
|
||||
extern uint16_t JWIDGET_FOCUS_IN;
|
||||
/* Downwards focus-out event: the widget has just lost focus */
|
||||
extern uint16_t JWIDGET_FOCUS_OUT;
|
||||
|
||||
//---
|
||||
// Creation and destruction
|
||||
//---
|
||||
|
||||
/* jwidget_create(): Create a widget
|
||||
|
||||
This function creates a type-less widget. If you want to create labels,
|
||||
buttons, input fields... you need to use the specific creation functions
|
||||
such as jlabel_create(). This function only creates empty widgets, which are
|
||||
primarily useful as containers.
|
||||
|
||||
If a non-NULL parent is specified, then the new widget becomes a child of
|
||||
that parent. The parent will then handle the positioning and sizing of the
|
||||
new widget, and destroy it automatically.
|
||||
|
||||
After creating a container with jwidget_create(), it is common to give it a
|
||||
layout and add children to it, either with jwidget_add_child() or through
|
||||
the children's constructors.
|
||||
|
||||
@parent This widget's parent.
|
||||
-> Returns the new widget (NULL on error). */
|
||||
jwidget *jwidget_create(void *parent);
|
||||
|
||||
/* jwidget_destroy(): Destroy a widget
|
||||
This function destroys the specified widget and its children. If the
|
||||
destroyed widget has a parent, the parent is notified, so the widget tree
|
||||
cannot become invalid. However, the layout process should be re-run to
|
||||
layout the remaining scene elements. */
|
||||
void jwidget_destroy(void *w);
|
||||
|
||||
//---
|
||||
// Widget tree manipulation
|
||||
//---
|
||||
|
||||
/* jwidget_set_parent(): Change a widget's parent
|
||||
Moves the widget from its current parent to another parent. If the widget
|
||||
already had a parent, it is notified. If the new parent is NULL, the widget
|
||||
is left without a parent. */
|
||||
void jwidget_set_parent(void *w, void *parent);
|
||||
|
||||
/* jwidget_add_child(): Add a child at the end of a widget's child list
|
||||
If the widget already had a parent, that parent is notified. */
|
||||
void jwidget_add_child(void *w, void *child);
|
||||
|
||||
/* jwidget_insert_child(): Insert a child in the widget's child list
|
||||
Similar to jwidget_add_child(), but the child is added at the requestd
|
||||
position in the parent's child list. The position must be in the range
|
||||
[0 ... w->child_count]. */
|
||||
void jwidget_insert_child(void *w, void *child, int position);
|
||||
|
||||
/* jwidget_remove_child(): Remove a child from a widget
|
||||
(w) must be the parent of (child). The child is left without a parent. */
|
||||
void jwidget_remove_child(void *w, void *child);
|
||||
|
||||
//---
|
||||
// Sizing and stretching
|
||||
//---
|
||||
|
||||
/* Functions to set the minimum width, minimum height, or both. The minimum
|
||||
size can be cleared by specifying 0. */
|
||||
void jwidget_set_minimum_width(void *w, int min_width);
|
||||
void jwidget_set_minimum_height(void *w, int min_height);
|
||||
void jwidget_set_minimum_size(void *w, int min_width, int min_height);
|
||||
|
||||
/* Functions to set the maximum width, maximum height, or both. The maximum
|
||||
size can be cleared by specifying -1. */
|
||||
void jwidget_set_maximum_width(void *w, int max_width);
|
||||
void jwidget_set_maximum_height(void *w, int max_height);
|
||||
void jwidget_set_maximum_size(void *w, int max_width, int max_height);
|
||||
|
||||
/* Functions to set both the minimum and maximum size at the same time. */
|
||||
void jwidget_set_fixed_width(void *w, int width);
|
||||
void jwidget_set_fixed_height(void *w, int height);
|
||||
void jwidget_set_fixed_size(void *w, int width, int height);
|
||||
|
||||
/* jwidget_set_stretch(): Set the stretch factors for a widget
|
||||
|
||||
Stretch factors indicate how much a widget wants to grow. Stretch is used in
|
||||
all size policies except the fixed one. Due to storage limits, the stretch
|
||||
factors should be in the range [0 ... 15].
|
||||
|
||||
The last parameter indicates whether to allow stretching beyond the maximum
|
||||
size of the widget. In almost all situations this should be false. However,
|
||||
in some cases you might want to completely ignore natural size and allocate
|
||||
space based uniquely on stretch factors. In this case, you can set a fixed
|
||||
size of 0 and enable stretching beyond limits. */
|
||||
void jwidget_set_stretch(void *w, int stretch_x, int stretch_y,
|
||||
bool stretch_beyond_limits);
|
||||
|
||||
//---
|
||||
// Geometry
|
||||
//--
|
||||
|
||||
/* jwidget_geometry_r(): Get read-only access to a widget's geometry
|
||||
|
||||
This function returns a read-only pointer to the specified widget's
|
||||
geometry. Because most widgets don't have customized geometry, JustUI
|
||||
doesn't store any data until the geometry is modified, to save memory. This
|
||||
is why it makes sense to separate read-only and read-write accesses.
|
||||
|
||||
For widgets without customized geometry, this functions returns a pointer to
|
||||
a fixed constant geometry with zero padding, border and margin. */
|
||||
jwidget_geometry const *jwidget_geometry_r(void *w);
|
||||
|
||||
/* jwidget_geometry_rw(): Get read-write access to a widget's geometry
|
||||
|
||||
This function returns a read-write pointer to the specified widget's
|
||||
geometry. For widgets that don't have customized geometry yet, this will
|
||||
duplicate the default settings. This avoids memory consumption on widgets
|
||||
that don't need custom geometry.
|
||||
|
||||
Returns NULL if duplication fails because of memory exhaustion. */
|
||||
jwidget_geometry *jwidget_geometry_rw(void *w);
|
||||
|
||||
/* jwidget_set_border(): Set a uniform border around a widget
|
||||
This is a shorthand to set (border_style), (border_color), and a uniform
|
||||
border width on a widget's geometry. */
|
||||
void jwidget_set_border(void *w, jwidget_border_style s, int width, int color);
|
||||
|
||||
/* jwidget_set_padding(): Set all padding distances around a widget */
|
||||
void jwidget_set_padding(void *w, int top, int right, int bottom, int left);
|
||||
|
||||
/* jwidget_set_margin(): Set all margin distances around a widget */
|
||||
void jwidget_set_margin(void *w, int top, int right, int bottom, int left);
|
||||
|
||||
/* jwidget_set_background(): Set the widget's background color
|
||||
The default is C_NONE. The background covers content and padding. */
|
||||
void jwidget_set_background(void *w, int color);
|
||||
|
||||
//---
|
||||
// Layout
|
||||
//---
|
||||
|
||||
/* jwidget_layout_dirty(): Check whether the tree needs to be laid out
|
||||
|
||||
This function checks the dirty bit of every widget in the tree. If any
|
||||
widget changes size, the whole tree needs to be laid out again (there are
|
||||
possible optimizations, but they are not implemented yet). */
|
||||
bool jwidget_layout_dirty(void *scene_root);
|
||||
|
||||
/* jwidget_layout(): Layout a widget tree
|
||||
|
||||
This function lays out the specified widget (computing its size and the
|
||||
position of its children) and its children recursively. Because this is a
|
||||
two-phase process going from the children to their parents and then from the
|
||||
parents to their children, it only makes sense to layout the whole tree at
|
||||
once. You should thus call jwidget_layout() only with your scene root.
|
||||
|
||||
A scene's layout should always be up-to-date before rendering. There is no
|
||||
need to layout at every frame (this would be a waste of resources), but you
|
||||
need to layout after doing any of the following things:
|
||||
|
||||
* Creating the scene
|
||||
* Adding, removing, or moving visible children around
|
||||
* Changing a widget's contents in a way that affects its natural size
|
||||
* Changing geometry or layout parameters on a widget
|
||||
|
||||
The layout process determines the size and position of every widget in the
|
||||
tree. Thus, if you need to access this size and position information, you
|
||||
need to keep the layout up-to-date before doing it. */
|
||||
void jwidget_layout(void *scene_root);
|
||||
|
||||
/* jwidget_width(): With of a widget's content box
|
||||
jwidget_height(): Height of a widget's content box
|
||||
|
||||
These functions return the size of the content box of a widget. The content
|
||||
box does not comprise the geometry (padding, border and margins). These
|
||||
dimensions are known only after layout; calling these functions when the
|
||||
layout is not up-to-date will return funny results. */
|
||||
int jwidget_content_width(void *w);
|
||||
int jwidget_content_height(void *w);
|
||||
|
||||
/* jwidget_full_width(): Width of a widget's margin box
|
||||
jwidget_full_height(): Height of a widget's margin box
|
||||
|
||||
These functions return the whole size of the margin box of a widgets; this
|
||||
includes the contents, padding, border and margins. These functions only
|
||||
make sense to call when the layout is up-to-date. */
|
||||
int jwidget_full_width(void *w);
|
||||
int jwidget_full_height(void *w);
|
||||
|
||||
//---
|
||||
// Rendering
|
||||
//---
|
||||
|
||||
/* jwidget_visible(): Whether widget is visible
|
||||
A non-visible widget occupies no space and is not rendered, as if it did not
|
||||
exist at all. */
|
||||
bool jwidget_visible(void *w);
|
||||
|
||||
/* jwidget_set_visible(): Hide or show a widget */
|
||||
void jwidget_set_visible(void *w, bool visible);
|
||||
|
||||
/* jwidget_needs_update(): Check whether the tree needs to be re-rendered
|
||||
|
||||
If this function returns true, you should re-render the tree. Aditionally,
|
||||
if jwidget_layout_dirty() returns true, you should re-layout the tree and
|
||||
repaint it. jscene_render() will layout automatically if needed, so you just
|
||||
need to call it if either function returns true.
|
||||
|
||||
When using jscene_run(), a JSCENE_REPAINT event will be emitted in this
|
||||
exact conditions, so just jscene_render() upon JSCENE_REPAINT. */
|
||||
bool jwidget_needs_update(void *w);
|
||||
|
||||
/* jwidget_render(): Render a widget
|
||||
This function renders the widget. The specified location (x,y) is the
|
||||
top-left corner of the margin box of the widget. There is no clipping.
|
||||
Unlike jscene_render(), this function does not automatically layout the
|
||||
widgets if there has been changes. */
|
||||
void jwidget_render(void *w, int x, int y);
|
||||
|
||||
//---
|
||||
// Misc
|
||||
//---
|
||||
|
||||
/* jwidget_type(): Get a widget's human-readable type name
|
||||
This is the name specified in the jwidgetPoly structure for the type. */
|
||||
char const *jwidget_type(void *w);
|
||||
|
||||
#endif /* _J_JWIDGET */
|
49
include/justui/p/vec.h
Normal file
49
include/justui/p/vec.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
//---
|
||||
// JustUI.util.vector: Dynamic arrays
|
||||
//---
|
||||
|
||||
#ifndef _J_UTIL_VECTOR
|
||||
#define _J_UTIL_VECTOR
|
||||
|
||||
#include <justui/defs.h>
|
||||
|
||||
/* Vector metadata, on four bytes; the pointer is included, but the user will
|
||||
have aliased union access from the macro definition */
|
||||
typedef struct {
|
||||
/* Pointer to elements of the vector */
|
||||
void *data;
|
||||
/* Number of elements used in the vector */
|
||||
uint16_t size;
|
||||
/* Number of free elements (never more than 255) */
|
||||
uint8_t free;
|
||||
/* Element size, in bytes */
|
||||
uint8_t elsize;
|
||||
|
||||
} vec_t;
|
||||
|
||||
/* Macro to declare a vector and have typed access to its data */
|
||||
#define DECLARE_VEC(type, name) \
|
||||
union { \
|
||||
type *name; \
|
||||
vec_t name ## _vec; \
|
||||
};
|
||||
|
||||
/* Initialize a vector; the element size can be specified with the typed
|
||||
pointer, like this: vec_init(&name_vector, sizeof *name); */
|
||||
void vec_init(vec_t *v, size_t elsize);
|
||||
|
||||
/* Free a vector's data, can also be used to clear all elements */
|
||||
void vec_clear(vec_t *v);
|
||||
|
||||
//---
|
||||
// Size management
|
||||
//---
|
||||
|
||||
/* Add elements to the vector. The elements should be assigned by the owner
|
||||
through the typed pointer, this only allocates and maintains the size. */
|
||||
bool vec_add(vec_t *v, size_t elements);
|
||||
|
||||
/* Remove elements from the vector */
|
||||
bool vec_remove(vec_t *v, size_t elements);
|
||||
|
||||
#endif /* _J_UTIL_VECTOR */
|
179
src/jfkeys.c
Normal file
179
src/jfkeys.c
Normal file
|
@ -0,0 +1,179 @@
|
|||
#include <justui/jfkeys.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
#include <gint/std/string.h>
|
||||
|
||||
/* Type identified for jfkeys */
|
||||
static int jfkeys_type_id = -1;
|
||||
|
||||
#ifdef FX9860G
|
||||
jfkeys *jfkeys_create(bopti_image_t const *img, void *parent)
|
||||
{
|
||||
if(jfkeys_type_id < 0) return NULL;
|
||||
|
||||
jfkeys *f = malloc(sizeof *f);
|
||||
jwidget_init(&f->widget, jfkeys_type_id, parent);
|
||||
jfkeys_set(f, img);
|
||||
return f;
|
||||
}
|
||||
|
||||
void jfkeys_set(jfkeys *f, bopti_image_t const *img)
|
||||
{
|
||||
f->img = img;
|
||||
f->level = 0;
|
||||
}
|
||||
#endif /* FX9860G */
|
||||
|
||||
#ifdef FXCG50
|
||||
jfkeys *jfkeys_create(char const *labels, void *parent)
|
||||
{
|
||||
if(jfkeys_type_id < 0) return NULL;
|
||||
|
||||
jfkeys *f = malloc(sizeof *f);
|
||||
jwidget_init(&f->widget, jfkeys_type_id, parent);
|
||||
jfkeys_set(f, labels);
|
||||
return f;
|
||||
}
|
||||
|
||||
void jfkeys_set(jfkeys *f, char const *labels)
|
||||
{
|
||||
f->labels = labels;
|
||||
f->level = 0;
|
||||
}
|
||||
|
||||
/* get_level(): Find the level inside a function definition */
|
||||
static char const *get_level(char const *labels, int level)
|
||||
{
|
||||
/* Navigate to level */
|
||||
while(level > 0) labels = strchrnul(labels, '|');
|
||||
return (*labels == 0) ? NULL : labels;
|
||||
}
|
||||
|
||||
/* get_label(): Find a key within a level */
|
||||
static char const *get_label(char const *level, int key, size_t *len)
|
||||
{
|
||||
int current_key = 0;
|
||||
|
||||
while(current_key <= key) {
|
||||
/* We reached the end of the level without finding the key */
|
||||
if(*level == 0 || *level == '|') return NULL;
|
||||
/* Next entry */
|
||||
if(*level == ';') {
|
||||
current_key++;
|
||||
level++;
|
||||
continue;
|
||||
}
|
||||
/* Found contents */
|
||||
if(current_key == key) {
|
||||
*len = 0;
|
||||
while(level[*len] != ';' && level[*len] != '|' && level[*len])
|
||||
(*len)++;
|
||||
return level;
|
||||
}
|
||||
|
||||
level++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif /* FXCG50 */
|
||||
|
||||
//---
|
||||
// Polymorphic widget operations
|
||||
//---
|
||||
|
||||
static void jfkeys_poly_csize(void *f0)
|
||||
{
|
||||
jfkeys *f = f0;
|
||||
|
||||
#ifdef FX9860G
|
||||
f->widget.w = 128;
|
||||
f->widget.h = 8;
|
||||
#endif
|
||||
|
||||
#ifdef FXCG50
|
||||
f->widget.w = 396;
|
||||
f->widget.h = 17;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void jfkeys_poly_render(void *f0, int base_x, int y)
|
||||
{
|
||||
jfkeys *f = f0;
|
||||
|
||||
#ifdef FX9860G
|
||||
dsubimage(base_x, y, f->img, 0, 9*f->level, f->img->width, 8, DIMAGE_NONE);
|
||||
#endif
|
||||
|
||||
#ifdef FXCG50
|
||||
font_t const *old_font = dfont(dfont_default());
|
||||
char const *level = get_level(f->labels, f->level);
|
||||
if(!level) return;
|
||||
|
||||
for(int position = 0; position < 6; position++) {
|
||||
size_t length = 0;
|
||||
char const *text = get_label(level, position, &length);
|
||||
if(!text || (*text != '.' && *text != '/' && *text != '@'
|
||||
&& *text != '#')) continue;
|
||||
|
||||
int x = base_x + 4 + 65 * position;
|
||||
int w = 63;
|
||||
int color = (text[0] == '#') ? C_BLACK : C_WHITE;
|
||||
|
||||
if(text[0] == '.') {
|
||||
drect(x, y, x + w - 1, y + 14, C_BLACK);
|
||||
}
|
||||
if(text[0] == '/' || text[0] == '@') {
|
||||
dline(x + 1, y, x + w - 2, y, C_BLACK);
|
||||
dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK);
|
||||
drect(x, y + 1, x + w - 1, y + 13, C_BLACK);
|
||||
}
|
||||
if(text[0] == '/') {
|
||||
dline(x + w - 1, y + 10, x + w - 5, y + 14, C_WHITE);
|
||||
dline(x + w - 1, y + 11, x + w - 4, y + 14, C_WHITE);
|
||||
dline(x + w - 1, y + 12, x + w - 3, y + 14, C_WHITE);
|
||||
dline(x + w - 1, y + 13, x + w - 2, y + 14, C_WHITE);
|
||||
}
|
||||
if(text[0] == '#') {
|
||||
dline(x + 1, y, x + w - 2, y, C_BLACK);
|
||||
dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK);
|
||||
drect(x, y + 1, x + 1, y + 13, C_BLACK);
|
||||
drect(x + w - 2, y + 1, x + w - 1, y + 13, C_BLACK);
|
||||
}
|
||||
|
||||
dtext_opt(x + (w >> 1), y + 3, color, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
||||
text + 1, length - 1);
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
#endif /* FXCG50 */
|
||||
}
|
||||
|
||||
int jfkeys_level(jfkeys *f)
|
||||
{
|
||||
return f->level;
|
||||
}
|
||||
|
||||
void jfkeys_set_level(jfkeys *f, int level)
|
||||
{
|
||||
if(f->level == level) return;
|
||||
f->level = level;
|
||||
f->widget.update = 1;
|
||||
}
|
||||
|
||||
/* jfkeys type definition */
|
||||
static jwidget_poly type_jfkeys = {
|
||||
.name = "jfkeys",
|
||||
.csize = jfkeys_poly_csize,
|
||||
.layout = NULL,
|
||||
.render = jfkeys_poly_render,
|
||||
.event = NULL,
|
||||
.destroy = NULL,
|
||||
};
|
||||
|
||||
/* Type registration */
|
||||
__attribute__((constructor(1005)))
|
||||
static void j_register_jfkeys(void)
|
||||
{
|
||||
jfkeys_type_id = j_register_widget(&type_jfkeys, "jwidget");
|
||||
}
|
351
src/jinput.c
Normal file
351
src/jinput.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <justui/jinput.h>
|
||||
#include "util.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
|
||||
/* Type identifier for jinput */
|
||||
static int jinput_type_id = -1;
|
||||
|
||||
/* Events */
|
||||
uint16_t JINPUT_VALIDATED;
|
||||
uint16_t JINPUT_CANCELED;
|
||||
|
||||
/* Input modes. Valid combinations are:
|
||||
* (SHIFT | ALPHA)? | SIMUL
|
||||
* (SHIFT or SHIFT_LOCK)? | (ALPHA or ALPHA_LOCK)? */
|
||||
enum {
|
||||
JINPUT_FLAT = 0x00,
|
||||
JINPUT_SHIFT = 0x01,
|
||||
JINPUT_ALPHA = 0x02,
|
||||
JINPUT_SHIFT_LOCK = 0x04,
|
||||
JINPUT_ALPHA_LOCK = 0x08,
|
||||
JINPUT_SIMUL = 0x10,
|
||||
};
|
||||
|
||||
/* Mode indicators and their size */
|
||||
extern bopti_image_t j_img_input_modes;
|
||||
#ifdef FX9860G
|
||||
#define JINPUT_INDICATOR 9
|
||||
#endif
|
||||
#ifdef FXCG50
|
||||
#define JINPUT_INDICATOR 12
|
||||
#endif
|
||||
|
||||
jinput *jinput_create(char const *prompt, size_t length, void *parent)
|
||||
{
|
||||
if(jinput_type_id < 0) return NULL;
|
||||
|
||||
jinput *i = malloc(sizeof *i);
|
||||
if(!i) return NULL;
|
||||
|
||||
jwidget_init(&i->widget, jinput_type_id, parent);
|
||||
|
||||
i->color = C_BLACK;
|
||||
jinput_set_font(i, NULL);
|
||||
i->prompt = (prompt ? prompt : "");
|
||||
|
||||
i->text = malloc(length + 1);
|
||||
i->text[0] = 0;
|
||||
i->size = 1;
|
||||
i->max = length + 1;
|
||||
i->cursor = -1;
|
||||
|
||||
i->mode = JINPUT_FLAT;
|
||||
i->timer = -1;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
void jinput_set_text_color(jinput *i, int color)
|
||||
{
|
||||
i->color = color;
|
||||
i->widget.update = 1;
|
||||
}
|
||||
|
||||
void jinput_set_font(jinput *i, font_t const *font)
|
||||
{
|
||||
if(!font) font = dfont_default();
|
||||
i->font = font;
|
||||
i->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jinput_set_prompt(jinput *i, char const *prompt)
|
||||
{
|
||||
i->prompt = prompt;
|
||||
i->widget.dirty = 1;
|
||||
}
|
||||
|
||||
//---
|
||||
// Input helpers
|
||||
//---
|
||||
|
||||
static void insert_str(jinput *i, char const *str, size_t n)
|
||||
{
|
||||
if(i->size + n > i->max) return;
|
||||
|
||||
/* Insert at cursor_pos, shift everything else right n places */
|
||||
for(int k = i->size - 1; k >= i->cursor; k--)
|
||||
i->text[k + n] = i->text[k];
|
||||
|
||||
for(int k = 0; k < (int)n; k++)
|
||||
i->text[i->cursor + k] = str[k];
|
||||
|
||||
i->size += n;
|
||||
i->cursor += n;
|
||||
}
|
||||
|
||||
static void insert_code_point(jinput *i, uint32_t p)
|
||||
{
|
||||
char str[4] = { 0x00, 0x80, 0x80, 0x80 };
|
||||
size_t size = 0;
|
||||
|
||||
if(p <= 0x7f) {
|
||||
str[0] = p;
|
||||
size = 1;
|
||||
}
|
||||
else if(p <= 0x7ff) {
|
||||
str[0] = 0xc0 | (p >> 6);
|
||||
str[1] |= (p & 0x3f);
|
||||
size = 2;
|
||||
}
|
||||
else if(p <= 0xffff) {
|
||||
str[0] = 0xe0 | (p >> 12);
|
||||
str[1] |= ((p > 6) & 0x3f);
|
||||
str[2] |= (p & 0x3f);
|
||||
size = 3;
|
||||
}
|
||||
else {
|
||||
str[0] = 0xf0 | (p >> 18);
|
||||
str[1] |= ((p >> 12) & 0x3f);
|
||||
str[2] |= ((p >> 6) & 0x3f);
|
||||
str[3] |= (p & 0x3f);
|
||||
size = 4;
|
||||
}
|
||||
|
||||
insert_str(i, str, size);
|
||||
}
|
||||
|
||||
static int previous(char const *str, int position)
|
||||
{
|
||||
if(position == 0)
|
||||
return position;
|
||||
|
||||
while((str[--position] & 0xc0) == 0x80) {}
|
||||
return position;
|
||||
}
|
||||
|
||||
static int next(char const *str, int position)
|
||||
{
|
||||
if(str[position] == 0) return position;
|
||||
|
||||
while((str[++position] & 0xc0) == 0x80) {}
|
||||
return position;
|
||||
}
|
||||
|
||||
static void delete(jinput *i)
|
||||
{
|
||||
int prev = previous(i->text, i->cursor);
|
||||
int diff = i->cursor - prev;
|
||||
if(!diff) return;
|
||||
|
||||
/* Move everything from (i->cursor) to (prev) */
|
||||
for(int k = prev; k < i->size - diff; k++) {
|
||||
i->text[k] = i->text[k + diff];
|
||||
}
|
||||
|
||||
i->size -= diff;
|
||||
i->cursor = prev;
|
||||
}
|
||||
|
||||
static void clear(jinput *i)
|
||||
{
|
||||
i->text[0] = 0;
|
||||
i->size = 1;
|
||||
i->cursor = 0;
|
||||
}
|
||||
|
||||
char const *jinput_value(jinput *i)
|
||||
{
|
||||
return i->text;
|
||||
}
|
||||
|
||||
void jinput_clear(jinput *i)
|
||||
{
|
||||
clear(i);
|
||||
}
|
||||
|
||||
//---
|
||||
// Polymorphic widget operations
|
||||
//---
|
||||
|
||||
static void jinput_poly_csize(void *i0)
|
||||
{
|
||||
jinput *i = i0;
|
||||
int w, h;
|
||||
|
||||
dsize(i->prompt, i->font, &w, &h);
|
||||
|
||||
i->widget.w = w + 16 + JINPUT_INDICATOR;
|
||||
i->widget.h = max(h, i->font->data_height);
|
||||
i->widget.h = max(i->widget.h, (int)j_img_input_modes.height);
|
||||
}
|
||||
|
||||
static void jinput_poly_render(void *i0, int x, int y)
|
||||
{
|
||||
jinput *i = i0;
|
||||
font_t const *old_font = dfont(i->font);
|
||||
int prompt_w, cursor_w, h;
|
||||
|
||||
dsize(i->prompt, i->font, &prompt_w, &h);
|
||||
dtext(x, y, i->color, i->prompt);
|
||||
|
||||
if(i->text) {
|
||||
dtext(x + prompt_w, y, i->color, i->text);
|
||||
}
|
||||
if(i->cursor >= 0) {
|
||||
if(i->text) dnsize(i->text, i->cursor, i->font, &cursor_w, NULL);
|
||||
else cursor_w = 0;
|
||||
|
||||
int cursor_x = x + prompt_w + cursor_w;
|
||||
dline(cursor_x, y, cursor_x, y + h - 1, i->color);
|
||||
#ifdef FXCG50
|
||||
dline(cursor_x + 1, y, cursor_x + 1, y + h - 1, i->color);
|
||||
#endif
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
|
||||
/* Mode indicators. Only one indicator is needed most of the time, unless
|
||||
one level is 1 and the other is 2. In this case, the temporary level (of
|
||||
value 1) is displayed instead. This leaves only 8 cases to deal with. */
|
||||
if(i->mode == 0) return;
|
||||
int sl = (i->mode & JINPUT_SHIFT_LOCK) ? 2 : (i->mode & JINPUT_SHIFT) != 0;
|
||||
int al = (i->mode & JINPUT_ALPHA_LOCK) ? 2 : (i->mode & JINPUT_ALPHA) != 0;
|
||||
int mode = (sl + al == 3) ? 5 : ((sl | al) + (al ? (sl ? 4 : 2) : 0));
|
||||
|
||||
x += jwidget_content_width(i) - JINPUT_INDICATOR;
|
||||
dsubimage(x, y, &j_img_input_modes, (JINPUT_INDICATOR + 1) * (mode - 1), 0,
|
||||
JINPUT_INDICATOR, j_img_input_modes.height, DIMAGE_NONE);
|
||||
}
|
||||
|
||||
static bool jinput_poly_event(void *i0, jevent e)
|
||||
{
|
||||
jinput *i = i0;
|
||||
|
||||
if(e.type == JWIDGET_FOCUS_IN) {
|
||||
i->cursor = i->size - 1;
|
||||
i->mode = 0;
|
||||
i->widget.update = 1;
|
||||
}
|
||||
|
||||
if(e.type == JWIDGET_FOCUS_OUT) {
|
||||
i->cursor = -1;
|
||||
i->mode = 0;
|
||||
i->widget.update = 1;
|
||||
}
|
||||
|
||||
if(e.type == JWIDGET_KEY) {
|
||||
key_event_t ev = e.key;
|
||||
|
||||
/* Releasing modifiers */
|
||||
if(ev.type == KEYEV_UP && ev.key == KEY_SHIFT) {
|
||||
if(i->mode & JINPUT_SIMUL) i->mode &= ~JINPUT_SHIFT;
|
||||
i->widget.update = 1;
|
||||
return true;
|
||||
}
|
||||
if(ev.type == KEYEV_UP && ev.key == KEY_ALPHA) {
|
||||
if(i->mode & JINPUT_SIMUL) i->mode &= ~JINPUT_ALPHA;
|
||||
i->widget.update = 1;
|
||||
return true;
|
||||
}
|
||||
if(i->mode == JINPUT_SIMUL) i->mode = 0;
|
||||
|
||||
if(ev.type != KEYEV_DOWN) return false;
|
||||
|
||||
if(ev.key == KEY_EXE) {
|
||||
jevent e = { .source = i, .type = JINPUT_VALIDATED };
|
||||
jwidget_emit(i, e);
|
||||
}
|
||||
else if(ev.key == KEY_EXIT) {
|
||||
clear(i);
|
||||
jevent e = { .source = i, .type = JINPUT_CANCELED };
|
||||
jwidget_emit(i, e);
|
||||
}
|
||||
else if(ev.key == KEY_RIGHT) {
|
||||
i->cursor = next(i->text, i->cursor);
|
||||
}
|
||||
else if(ev.key == KEY_LEFT) {
|
||||
i->cursor = previous(i->text, i->cursor);
|
||||
}
|
||||
else if(ev.key == KEY_DEL) {
|
||||
delete(i);
|
||||
}
|
||||
else if(ev.key == KEY_ACON) {
|
||||
clear(i);
|
||||
}
|
||||
else if(ev.key == KEY_SHIFT) {
|
||||
if(i->mode & JINPUT_SHIFT_LOCK)
|
||||
i->mode ^= JINPUT_SHIFT_LOCK;
|
||||
else if(i->mode & JINPUT_SHIFT && !(i->mode & JINPUT_SIMUL))
|
||||
i->mode ^= JINPUT_SHIFT | JINPUT_SHIFT_LOCK;
|
||||
else
|
||||
i->mode |= JINPUT_SHIFT;
|
||||
}
|
||||
else if(ev.key == KEY_ALPHA) {
|
||||
if(i->mode & JINPUT_ALPHA_LOCK)
|
||||
i->mode ^= JINPUT_ALPHA_LOCK;
|
||||
else if(i->mode & JINPUT_ALPHA && !(i->mode & JINPUT_SIMUL))
|
||||
i->mode ^= JINPUT_ALPHA | JINPUT_ALPHA_LOCK;
|
||||
else
|
||||
i->mode |= JINPUT_ALPHA;
|
||||
}
|
||||
else {
|
||||
uint32_t code_point = keymap_translate(ev.key,
|
||||
(i->mode & JINPUT_SHIFT) || (i->mode & JINPUT_SHIFT_LOCK),
|
||||
(i->mode & JINPUT_ALPHA) || (i->mode & JINPUT_ALPHA_LOCK)
|
||||
);
|
||||
if(code_point) {
|
||||
insert_code_point(i, code_point);
|
||||
/* Mark modifiers as simultaneous if they're not released */
|
||||
if((keydown(KEY_SHIFT) && !(i->mode & JINPUT_SHIFT_LOCK)) ||
|
||||
(keydown(KEY_ALPHA) && !(i->mode & JINPUT_ALPHA_LOCK)))
|
||||
i->mode |= JINPUT_SIMUL;
|
||||
/* Remove modifiers otherwise */
|
||||
else i->mode &= ~(JINPUT_SHIFT | JINPUT_ALPHA);
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
i->widget.update = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void jinput_poly_destroy(void *i0)
|
||||
{
|
||||
jinput *i = i0;
|
||||
free(i->text);
|
||||
}
|
||||
|
||||
/* jinput type definition */
|
||||
static jwidget_poly type_jinput = {
|
||||
.name = "jinput",
|
||||
.csize = jinput_poly_csize,
|
||||
.layout = NULL,
|
||||
.render = jinput_poly_render,
|
||||
.event = jinput_poly_event,
|
||||
.destroy = jinput_poly_destroy,
|
||||
};
|
||||
|
||||
__attribute__((constructor(1003)))
|
||||
static void j_register_jinput(void)
|
||||
{
|
||||
jinput_type_id = j_register_widget(&type_jinput, "jwidget");
|
||||
JINPUT_VALIDATED = j_register_event();
|
||||
JINPUT_CANCELED = j_register_event();
|
||||
}
|
360
src/jlabel.c
Normal file
360
src/jlabel.c
Normal file
|
@ -0,0 +1,360 @@
|
|||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <justui/jlabel.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/std/stdio.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
#include <gint/std/string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* Type identifier for jlabel */
|
||||
static int jlabel_type_id = -1;
|
||||
|
||||
jlabel *jlabel_create(char const *text, void *parent)
|
||||
{
|
||||
if(jlabel_type_id < 0) return NULL;
|
||||
|
||||
jlabel *l = malloc(sizeof *l);
|
||||
if(!l) return NULL;
|
||||
|
||||
jwidget_init(&l->widget, jlabel_type_id, parent);
|
||||
|
||||
l->block_halign = J_ALIGN_LEFT;
|
||||
l->block_valign = J_ALIGN_MIDDLE;
|
||||
l->text_align = J_ALIGN_LEFT;
|
||||
|
||||
l->line_spacing = 1;
|
||||
l->color = C_BLACK;
|
||||
l->font = NULL;
|
||||
|
||||
l->text = text;
|
||||
l->owns_text = false;
|
||||
|
||||
vec_init(&l->breaks_vec, sizeof *l->breaks);
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Revert the label to an empty string. */
|
||||
static void remove_text(jlabel *l)
|
||||
{
|
||||
if(l->owns_text) free((char *)l->text);
|
||||
l->text = NULL;
|
||||
l->owns_text = false;
|
||||
vec_clear(&l->breaks_vec);
|
||||
}
|
||||
|
||||
/* Add a breaking point at the specified string offset. */
|
||||
static void add_break(jlabel *l, uint16_t br)
|
||||
{
|
||||
if(!vec_add(&l->breaks_vec, 1)) return;
|
||||
l->breaks[l->breaks_vec.size - 1] = br;
|
||||
}
|
||||
|
||||
static char const *word_boundary(char const *start, char const *cursor, bool
|
||||
look_ahead)
|
||||
{
|
||||
char const *str = cursor;
|
||||
|
||||
/* Look for a word boundary behind the cursor */
|
||||
while(1) {
|
||||
/* Current position is end-of-string: suitable */
|
||||
if(*str == 0) return str;
|
||||
/* Current position is start of string: bad */
|
||||
if(str <= start) break;
|
||||
|
||||
/* Look for heteregoneous neighboring characters */
|
||||
int space_l = (str[-1] == ' ' || str[-1] == '\n');
|
||||
int space_r = (str[0] == ' ' || str[0] == '\n');
|
||||
|
||||
if(!space_l && space_r) return str;
|
||||
str--;
|
||||
}
|
||||
|
||||
/* If we can't look ahead, return the starting position to force a cut */
|
||||
if(!look_ahead) return cursor;
|
||||
str++;
|
||||
|
||||
/* Otherwise, look ahead */
|
||||
while(*str) {
|
||||
int space_l = (str[-1] == ' ' || str[-1] == '\n');
|
||||
int space_r = (str[0] == ' ' || str[0] == '\n');
|
||||
|
||||
if(!space_l && space_r) return str;
|
||||
str++;
|
||||
}
|
||||
|
||||
/* If there's really nothing, return end-of-string */
|
||||
return str;
|
||||
}
|
||||
|
||||
//---
|
||||
// Text manipulation
|
||||
//---
|
||||
|
||||
void jlabel_set_text(jlabel *l, char const *text)
|
||||
{
|
||||
remove_text(l);
|
||||
l->text = text;
|
||||
l->owns_text = false;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
int jlabel_asprintf(jlabel *l, char const *format, ...)
|
||||
{
|
||||
remove_text(l);
|
||||
char *text = NULL;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int count = vasprintf(&text, format, args);
|
||||
va_end(args);
|
||||
|
||||
l->text = text;
|
||||
l->owns_text = (text != NULL && count >= 0);
|
||||
l->widget.dirty = 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
int jlabel_snprintf(jlabel *l, size_t size, char const *format, ...)
|
||||
{
|
||||
char *text = malloc(size + 1);
|
||||
if(!text) return -1;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int count = vsnprintf(text, size, format, args);
|
||||
va_end(args);
|
||||
|
||||
l->text = text;
|
||||
l->owns_text = true;
|
||||
l->widget.dirty = 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
char const *jlabel_text(jlabel *l)
|
||||
{
|
||||
return l->text;
|
||||
}
|
||||
|
||||
//---
|
||||
// Property setters
|
||||
//---
|
||||
|
||||
void jlabel_set_block_alignment(jlabel *l, jalign horz, jalign vert)
|
||||
{
|
||||
l->block_halign = horz;
|
||||
l->block_valign = vert;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_text_alignment(jlabel *l, jalign align)
|
||||
{
|
||||
l->text_align = align;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_alignment(jlabel *l, jalign horz)
|
||||
{
|
||||
l->block_halign = horz;
|
||||
l->text_align = horz;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_line_spacing(jlabel *l, int line_spacing)
|
||||
{
|
||||
l->line_spacing = line_spacing;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_wrap_mode(jlabel *l, jwrapmode mode)
|
||||
{
|
||||
l->wrap_mode = mode;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_text_color(jlabel *l, int color)
|
||||
{
|
||||
l->color = color;
|
||||
l->widget.update = 1;
|
||||
}
|
||||
|
||||
void jlabel_set_font(jlabel *l, font_t const *font)
|
||||
{
|
||||
/* A NULL font is acceptable here, dfont() will handle it later */
|
||||
l->font = font;
|
||||
l->widget.dirty = 1;
|
||||
}
|
||||
|
||||
//---
|
||||
// Polymorphic widget operations
|
||||
//---
|
||||
|
||||
static void jlabel_poly_csize(void *l0)
|
||||
{
|
||||
jlabel *l = l0;
|
||||
jwidget *w = &l->widget;
|
||||
w->w = 0;
|
||||
w->h = 0;
|
||||
|
||||
font_t const *old_font = dfont(l->font);
|
||||
|
||||
/* Cut at newline characters */
|
||||
char const *str = l->text;
|
||||
while(*str)
|
||||
{
|
||||
char const *end_of_line = strchrnul(str, '\n');
|
||||
int line_w, line_h;
|
||||
|
||||
dnsize(str, end_of_line - str, NULL, &line_w, &line_h);
|
||||
w->w = max(w->w, line_w);
|
||||
w->h += (w->h > 0 ? l->line_spacing : 0) + line_h;
|
||||
|
||||
str = end_of_line + (*end_of_line == '\n');
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
}
|
||||
|
||||
static void jlabel_poly_layout(void *l0)
|
||||
{
|
||||
jlabel *l = l0;
|
||||
vec_clear(&l->breaks_vec);
|
||||
|
||||
int cw = jwidget_content_width(l);
|
||||
|
||||
/* Configure the font for dnsize() below; we can't pass l->font directly as
|
||||
NULL would default to the current font rather than gint's default */
|
||||
font_t const *old_font = dfont(l->font);
|
||||
|
||||
/* Determine the end of each line, influenced by the wrap mode */
|
||||
char const *str = l->text;
|
||||
while(*str)
|
||||
{
|
||||
/* Start of line */
|
||||
add_break(l, str - l->text);
|
||||
|
||||
/* A "\n" forces a newline in all wrap omdes */
|
||||
char const *end_of_line = strchrnul(str, '\n');
|
||||
|
||||
/* Also consider word or letters boundaries */
|
||||
if(l->wrap_mode != J_WRAP_NONE) {
|
||||
char const *end_of_widget = drsize(str, NULL, cw, NULL);
|
||||
|
||||
if(end_of_widget < end_of_line) {
|
||||
/* In WRAP_LETTER mode, stop exactly at the limiting letter */
|
||||
if(l->wrap_mode == J_WRAP_LETTER)
|
||||
end_of_line = end_of_widget;
|
||||
/* In WRAP_WORD, try to find a word boundary behind; if this
|
||||
fails, we fall back to end_of_line */
|
||||
else if(l->wrap_mode == J_WRAP_WORD)
|
||||
end_of_line = word_boundary(str, end_of_widget, false);
|
||||
/* In WRAP_WORD_ONLY, we want a word boundary, even if ahead */
|
||||
else if(l->wrap_mode == J_WRAP_WORD_ONLY)
|
||||
end_of_line = word_boundary(str, end_of_widget, true);
|
||||
}
|
||||
}
|
||||
|
||||
char const *next_start = end_of_line + (*end_of_line == '\n');
|
||||
|
||||
/* Skip trailing spaces on this line */
|
||||
while(end_of_line > str && end_of_line[-1] == ' ')
|
||||
end_of_line--;
|
||||
/* Skip leading spaces on the next line */
|
||||
while(next_start[0] == ' ')
|
||||
next_start++;
|
||||
|
||||
add_break(l, end_of_line - l->text);
|
||||
|
||||
/* Compute the length of this line (this is needed even if drsize() has
|
||||
been used since spaces may have been removed) */
|
||||
int line_width;
|
||||
dnsize(str, end_of_line - str, NULL, &line_width, NULL);
|
||||
|
||||
l->block_width = max(l->block_width, line_width);
|
||||
str = next_start;
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
}
|
||||
|
||||
static void jlabel_poly_render(void *l0, int x, int y)
|
||||
{
|
||||
jlabel *l = l0;
|
||||
font_t const *old_font = dfont(l->font);
|
||||
/* Set the font again; this returns l->font most of the time, except when
|
||||
l->font is NULL, in which case it gives the default font's address*/
|
||||
font_t const *f = dfont(l->font);
|
||||
|
||||
/* Available content width and height */
|
||||
int cw = jwidget_content_width(l);
|
||||
int ch = jwidget_content_height(l);
|
||||
|
||||
/* Position the block vertically */
|
||||
|
||||
int lines = l->breaks_vec.size / 2;
|
||||
int block_height = lines * (f->line_height + l->line_spacing) -
|
||||
l->line_spacing;
|
||||
|
||||
if(l->block_valign == J_ALIGN_MIDDLE)
|
||||
y += (ch - block_height) / 2;
|
||||
if(l->block_valign == J_ALIGN_BOTTOM)
|
||||
y += ch - block_height;
|
||||
|
||||
/* Position the block horizontally */
|
||||
|
||||
if(l->block_halign == J_ALIGN_CENTER)
|
||||
x += (cw - l->block_width) / 2;
|
||||
if(l->block_halign == J_ALIGN_RIGHT)
|
||||
x += cw - l->block_width;
|
||||
|
||||
/* Render lines */
|
||||
|
||||
for(int i = 0; i < lines; i++) {
|
||||
char const *str = l->text + l->breaks[2*i];
|
||||
int line_length = l->breaks[2*i+1] - l->breaks[2*i];
|
||||
|
||||
/* Handle horizontal alignment */
|
||||
int dx = 0;
|
||||
if(l->text_align != J_ALIGN_LEFT) {
|
||||
int line_width;
|
||||
dnsize(str, line_length, NULL, &line_width, NULL);
|
||||
|
||||
if(l->text_align == J_ALIGN_CENTER)
|
||||
dx = (l->block_width - line_width) / 2;
|
||||
if(l->text_align == J_ALIGN_RIGHT)
|
||||
dx = l->block_width - line_width;
|
||||
}
|
||||
|
||||
dtext_opt(x + dx, y, l->color, C_NONE, DTEXT_LEFT, DTEXT_TOP,
|
||||
str, line_length);
|
||||
|
||||
y += f->line_height + l->line_spacing;
|
||||
str = l->text + l->breaks[i];
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
}
|
||||
|
||||
static void jlabel_poly_destroy(void *l)
|
||||
{
|
||||
remove_text(l);
|
||||
}
|
||||
|
||||
/* jlabel type definition */
|
||||
static jwidget_poly type_jlabel = {
|
||||
.name = "jlabel",
|
||||
.csize = jlabel_poly_csize,
|
||||
.layout = jlabel_poly_layout,
|
||||
.render = jlabel_poly_render,
|
||||
.event = NULL,
|
||||
.destroy = jlabel_poly_destroy,
|
||||
};
|
||||
|
||||
/* Type registration */
|
||||
__attribute__((constructor(1002)))
|
||||
static void j_register_jlabel(void)
|
||||
{
|
||||
jlabel_type_id = j_register_widget(&type_jlabel, "jwidget");
|
||||
}
|
349
src/jlayout_box.c
Normal file
349
src/jlayout_box.c
Normal file
|
@ -0,0 +1,349 @@
|
|||
#include <justui/jlayout.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include "jlayout_p.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <openlibm.h>
|
||||
|
||||
//---
|
||||
// Flexbox-like box layout
|
||||
//---
|
||||
|
||||
jlayout_box *jlayout_get_hbox(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return (w->layout == J_LAYOUT_HBOX) ? &w->layout_box : NULL;
|
||||
}
|
||||
|
||||
jlayout_box *jlayout_get_vbox(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return (w->layout == J_LAYOUT_VBOX) ? &w->layout_box : NULL;
|
||||
}
|
||||
|
||||
jlayout_box *jlayout_set_hbox(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->layout = J_LAYOUT_HBOX;
|
||||
jlayout_box *l = &w->layout_box;
|
||||
|
||||
l->spacing = 0;
|
||||
return l;
|
||||
}
|
||||
|
||||
jlayout_box *jlayout_set_vbox(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->layout = J_LAYOUT_VBOX;
|
||||
jlayout_box *l = &w->layout_box;
|
||||
|
||||
l->spacing = 0;
|
||||
return l;
|
||||
}
|
||||
|
||||
void jlayout_box_csize(void *w0)
|
||||
{
|
||||
/* Compute as if the layout is vertical first */
|
||||
J_CAST(w)
|
||||
jlayout_box *l = &w->layout_box;
|
||||
int horiz = (w->layout == J_LAYOUT_HBOX);
|
||||
|
||||
int main = 0;
|
||||
int cross = 0;
|
||||
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
jwidget *child = w->children[k];
|
||||
if(!child->visible) continue;
|
||||
jwidget_msize(child);
|
||||
|
||||
main += (k > 0 ? l->spacing : 0) + (horiz ? child->w : child->h);
|
||||
cross = max(cross, (horiz ? child->h : child->w));
|
||||
}
|
||||
|
||||
if(horiz) {
|
||||
w->w = main;
|
||||
w->h = cross;
|
||||
}
|
||||
else {
|
||||
w->w = cross;
|
||||
w->h = main;
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Distribution system
|
||||
//
|
||||
// The basic idea of space redistribution is to give each widget extra space
|
||||
// proportional to their stretch rates in the relevant direction. However, the
|
||||
// addition of maximum size constraints means that widgets can decline some of
|
||||
// the extra space being allocated.
|
||||
//
|
||||
// This system defines the result of expansion as a function of the "expansion
|
||||
// factor". As the expansion factor increases, every widget stretches at a
|
||||
// speed proportional to its stretch rate, until it reaches its maximum size.
|
||||
//
|
||||
// Extra widget size
|
||||
// |
|
||||
// + .-------- Maximum size
|
||||
// | .`
|
||||
// | .` <- Slope: widget stretch rate
|
||||
// |.`
|
||||
// 0 +-------+------> Expansion factor
|
||||
// 0 ^
|
||||
// Breaking point
|
||||
//
|
||||
// The extra space allocated to widgets is the sum of this function for every
|
||||
// widget considered for expansion. Since every widget has a possibly different
|
||||
// breaking point, a maximal interval of expansion factor that has no breaking
|
||||
// point is called a "run". During each run, the slope for the total space
|
||||
// remains constant, and a unit of expansion factor corresponds to one pixel
|
||||
// being allocated in the container. Thus, whenever the expansion factor
|
||||
// increases of (slope), every widget (w) gets (w->stretch) new pixels.
|
||||
//
|
||||
// The functions below simulate the expansion by determining the breaking
|
||||
// points of the widgets and allocating extra space during each run. Once the
|
||||
// total extra space allocated reaches the available space, simulation stops
|
||||
// and the allocation is recorded by assigning actual size to widgets.
|
||||
//---
|
||||
|
||||
/* This "expansion" structure tracks information relating to a single child
|
||||
widget during the space distribution process. */
|
||||
typedef struct {
|
||||
/* Child index */
|
||||
uint8_t id;
|
||||
/* Stretch rate, sum of stretch rates is the "slope" */
|
||||
uint8_t stretch;
|
||||
/* Maximum size augmentation */
|
||||
int16_t max;
|
||||
/* Extra space allocate in the previous runs, in pixels */
|
||||
float allocated;
|
||||
/* Breaking point for the current run, as a number of pixels to distribute
|
||||
to the whole system */
|
||||
float breaking_point;
|
||||
} exp_t;
|
||||
|
||||
/* Determine whether a widget can expand any further. */
|
||||
static bool can_expand(exp_t *e)
|
||||
{
|
||||
return (e->stretch > 0 && e->allocated < e->max);
|
||||
}
|
||||
|
||||
/* Compute the slope for the current run. */
|
||||
static uint compute_slope(exp_t elements[], size_t n)
|
||||
{
|
||||
uint slope = 0;
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
if(can_expand(&elements[i])) slope += elements[i].stretch;
|
||||
}
|
||||
return slope;
|
||||
}
|
||||
|
||||
/* Compute the breaking point for every expanding widget. Returns the amount of
|
||||
pixels to allocate in order to reach the next breaking point. */
|
||||
static float compute_breaking_points(exp_t elements[], size_t n, uint slope)
|
||||
{
|
||||
float closest = HUGE_VALF;
|
||||
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
if(!can_expand(e)) continue;
|
||||
|
||||
/* Up to (e->max - e->allocated) pixels can be added to this widget.
|
||||
With the factor of (slope / e->stretch), we get the number of pixels
|
||||
to add to the container in order to reach the threshold. */
|
||||
e->breaking_point = (e->max - e->allocated) * (slope / e->stretch);
|
||||
closest = min(e->breaking_point, closest);
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
/* Allocate floating-point space to widgets. This is the core of the
|
||||
distribution system, it produces (e->allocated) for every element. */
|
||||
static void allocate_space(exp_t elements[], size_t n, float available)
|
||||
{
|
||||
/* One iteration per run */
|
||||
while(available > 0) {
|
||||
/* Slope for this run; if zero, no more widget can grow */
|
||||
uint slope = compute_slope(elements, n);
|
||||
if(!slope) break;
|
||||
|
||||
/* Closest breaking point, amount of space to distribute this run */
|
||||
float breaking = compute_breaking_points(elements, n, slope);
|
||||
float run_budget = min(breaking, available);
|
||||
|
||||
/* Give everyone their share of run_budget */
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
if(!can_expand(e)) continue;
|
||||
|
||||
e->allocated += (run_budget * e->stretch) / slope;
|
||||
|
||||
/* Avoid floating-point errors when reaching the maximum size: test
|
||||
(e->breaking) instead of (e->allocated), and assign (e->max) to
|
||||
eliminate risks of rounding down */
|
||||
if(e->breaking_point == run_budget) e->allocated = e->max;
|
||||
}
|
||||
|
||||
available -= run_budget;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stable insertion sort: order children by decreasing fractional allocation */
|
||||
static void sort_by_fractional_allocation(exp_t elements[], size_t n)
|
||||
{
|
||||
for(size_t spot = 0; spot < n - 1; spot++) {
|
||||
/* Find the element with the max fractional value in [spot..size] */
|
||||
float max_frac = 0;
|
||||
int max_frac_who = -1;
|
||||
|
||||
for(size_t i = spot; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
|
||||
float frac = e->allocated - floorf(e->allocated);
|
||||
|
||||
if(max_frac_who < 0 || frac > max_frac) {
|
||||
max_frac = frac;
|
||||
max_frac_who = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Give that element the spot */
|
||||
exp_t temp = elements[spot];
|
||||
elements[spot] = elements[max_frac_who];
|
||||
elements[max_frac_who] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Round allocations so that they add up to the available space */
|
||||
static void round_allocations(exp_t elements[], size_t n, int available_space)
|
||||
{
|
||||
/* Prepare to give everyone the floor of their allocation */
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
available_space -= floorf(e->allocated);
|
||||
}
|
||||
|
||||
/* Sort by decreasing fractional allocation then add one extra pixel to
|
||||
the (available_space) children with highest fractional allocation */
|
||||
sort_by_fractional_allocation(elements, n);
|
||||
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
e->allocated = floorf(e->allocated);
|
||||
|
||||
if(can_expand(e) && (int)i < available_space) e->allocated += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void jlayout_box_apply(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
jlayout_box const *l = &w->layout_box;
|
||||
int horiz = (w->layout == J_LAYOUT_HBOX);
|
||||
|
||||
if(!w->child_count) return;
|
||||
|
||||
/* Content width and height */
|
||||
int cw = jwidget_content_width(w);
|
||||
int ch = jwidget_content_height(w);
|
||||
/* Allocatable width and height (which excludes spacing) */
|
||||
int total_spacing = (w->child_count - 1) * l->spacing;
|
||||
int aw = cw - (horiz ? total_spacing : 0);
|
||||
int ah = ch - (horiz ? 0 : total_spacing);
|
||||
/* Length along the main axis, including spacing */
|
||||
int length = 0;
|
||||
|
||||
/* Expanding widgets' information for extra space distribution */
|
||||
size_t n = w->child_count;
|
||||
exp_t elements[n];
|
||||
|
||||
bool has_started = false;
|
||||
for(size_t i = 0; i < w->child_count; i++) {
|
||||
jwidget *child = w->children[i];
|
||||
|
||||
/* Maximum size to enforce: this is the acceptable size closest to our
|
||||
allocatable size */
|
||||
int max_w = clamp(aw, child->min_w, child->max_w);
|
||||
int max_h = clamp(ah, child->min_h, child->max_h);
|
||||
|
||||
/* Start by setting every child to an acceptable size */
|
||||
child->w = clamp(child->w, child->min_w, max_w);
|
||||
child->h = clamp(child->h, child->min_h, max_h);
|
||||
|
||||
/* Initialize expanding widgets' information */
|
||||
elements[i].id = i;
|
||||
elements[i].allocated = 0.0f;
|
||||
elements[i].breaking_point = -1.0f;
|
||||
|
||||
/* Determine natural length along the container, and stretch child
|
||||
along the perpendicular direction if possible */
|
||||
|
||||
if(!child->visible) {
|
||||
elements[i].stretch = 0;
|
||||
elements[i].max = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(has_started) length += l->spacing;
|
||||
if(horiz) {
|
||||
length += child->w;
|
||||
if(child->stretch_y > 0) child->h = max_h;
|
||||
|
||||
elements[i].stretch = child->stretch_x;
|
||||
elements[i].max = max(max_w - child->w, 0);
|
||||
|
||||
if(child->stretch_force && child->stretch_x > 0)
|
||||
elements[i].max = max(aw - child->w, 0);
|
||||
}
|
||||
else {
|
||||
length += child->h;
|
||||
if(child->stretch_x > 0) child->w = max_w;
|
||||
|
||||
elements[i].stretch = child->stretch_y;
|
||||
elements[i].max = max(max_h - child->h, 0);
|
||||
|
||||
if(child->stretch_force && child->stretch_y > 0)
|
||||
elements[i].max = max(ah - child->h, 0);
|
||||
}
|
||||
|
||||
has_started = true;
|
||||
}
|
||||
|
||||
/* Distribute extra space along the line */
|
||||
int extra_space = (horiz ? cw : ch) - length;
|
||||
allocate_space(elements, n, extra_space);
|
||||
round_allocations(elements, n, extra_space);
|
||||
|
||||
/* Update widgets for extra space */
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
exp_t *e = &elements[i];
|
||||
jwidget *child = w->children[e->id];
|
||||
if(!child->visible) continue;
|
||||
|
||||
if(horiz)
|
||||
child->w += e->allocated;
|
||||
else
|
||||
child->h += e->allocated;
|
||||
}
|
||||
|
||||
/* Position everyone */
|
||||
int position = 0;
|
||||
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
jwidget *child = w->children[i];
|
||||
if(!child->visible) continue;
|
||||
|
||||
if(horiz) {
|
||||
child->x = position;
|
||||
child->y = (ch - child->h) / 2;
|
||||
position += child->w + l->spacing;
|
||||
}
|
||||
else {
|
||||
child->x = (cw - child->w) / 2;
|
||||
child->y = position;
|
||||
position += child->h + l->spacing;
|
||||
}
|
||||
}
|
||||
}
|
17
src/jlayout_grid.c
Normal file
17
src/jlayout_grid.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <justui/jlayout.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include "jlayout_p.h"
|
||||
|
||||
/* TODO: Functions for grid layouts */
|
||||
|
||||
void jlayout_grid_csize(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->w = 0;
|
||||
w->h = 0;
|
||||
}
|
||||
|
||||
void jlayout_grid_apply(GUNUSED void *w0)
|
||||
{
|
||||
}
|
31
src/jlayout_p.h
Normal file
31
src/jlayout_p.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//---
|
||||
// JustUI.jlayout (private): Internal layout functions
|
||||
//---
|
||||
|
||||
#ifndef _J_JLAYOUT_P
|
||||
#define _J_JLAYOUT_P
|
||||
|
||||
/* jlayout_*_csize(): Natural size of content box for widgets with layouts
|
||||
|
||||
This function finds the size of the children of (w) with jwidget_msize(),
|
||||
and then deduces the natural content size of (w). The results are stored in
|
||||
(w->w) and (w->h), as usual for the first phase of the layout process.
|
||||
|
||||
This function is called only by jwidget_msize() during layout. */
|
||||
void jlayout_box_csize(void *w);
|
||||
void jlayout_stack_csize(void *w);
|
||||
void jlayout_grid_csize(void *w);
|
||||
|
||||
/* jlayout_*_apply(): Layout widgets with layouts
|
||||
|
||||
This function is essentially the second phase of the layout process for
|
||||
widgets with layouts. Given that the margin-box size is set in (w->w) and
|
||||
(w->h), the layout splits available content space between children and
|
||||
positions them, before calling jwidget_layout() on the children.
|
||||
|
||||
This function is called only by jwidget_layout() during layout. */
|
||||
void jlayout_box_apply(void *w);
|
||||
void jlayout_stack_apply(void *w);
|
||||
void jlayout_grid_apply(void *w);
|
||||
|
||||
#endif /* _J_JLAYOUT_P */
|
66
src/jlayout_stack.c
Normal file
66
src/jlayout_stack.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <justui/jlayout.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include "jlayout_p.h"
|
||||
#include "util.h"
|
||||
|
||||
jlayout_stack *jlayout_get_stack(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return (w->layout == J_LAYOUT_STACK) ? &w->layout_stack : NULL;
|
||||
}
|
||||
|
||||
jlayout_stack *jlayout_set_stack(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->layout = J_LAYOUT_STACK;
|
||||
jlayout_stack *l = &w->layout_stack;
|
||||
|
||||
l->active = -1;
|
||||
return l;
|
||||
}
|
||||
|
||||
void jlayout_stack_csize(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->w = 0;
|
||||
w->h = 0;
|
||||
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
jwidget *child = w->children[k];
|
||||
if(!child->visible) continue;
|
||||
jwidget_msize(child);
|
||||
|
||||
w->w = max(w->w, child->w);
|
||||
w->h = max(w->h, child->h);
|
||||
}
|
||||
}
|
||||
|
||||
void jlayout_stack_apply(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
|
||||
int cw = jwidget_content_width(w);
|
||||
int ch = jwidget_content_height(w);
|
||||
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
jwidget *child = w->children[k];
|
||||
|
||||
/* Maximum size to enforce: this is the acceptable size closest to our
|
||||
content size (that space we have to distribute) */
|
||||
int max_w = clamp(cw, child->min_w, child->max_w);
|
||||
int max_h = clamp(ch, child->min_h, child->max_h);
|
||||
|
||||
/* Set every child to an acceptable size */
|
||||
child->w = clamp(child->w, child->min_w, max_w);
|
||||
child->h = clamp(child->h, child->min_h, max_h);
|
||||
|
||||
/* Expand each child if any level of stretch is specified */
|
||||
if(child->stretch_x > 0) child->w = max_w;
|
||||
if(child->stretch_y > 0) child->h = max_h;
|
||||
|
||||
/* Center each child in the container */
|
||||
child->x = (cw - child->w) / 2;
|
||||
child->y = (ch - child->h) / 2;
|
||||
}
|
||||
}
|
55
src/jpainted.c
Normal file
55
src/jpainted.c
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include <justui/jpainted.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
|
||||
/* Type identified for jpainted */
|
||||
static int jpainted_type_id = -1;
|
||||
|
||||
jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w,
|
||||
int natural_h, void *parent)
|
||||
{
|
||||
if(jpainted_type_id < 0) return NULL;
|
||||
|
||||
jpainted *p = malloc(sizeof *p);
|
||||
jwidget_init(&p->widget, jpainted_type_id, parent);
|
||||
|
||||
p->paint = function;
|
||||
p->arg = arg;
|
||||
p->natural_w = natural_w;
|
||||
p->natural_h = natural_h;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
//---
|
||||
// Polymorphic widget operations
|
||||
//---
|
||||
|
||||
static void jpainted_poly_csize(void *p0)
|
||||
{
|
||||
jpainted *p = p0;
|
||||
p->widget.w = p->natural_w;
|
||||
p->widget.h = p->natural_h;
|
||||
}
|
||||
|
||||
static void jpainted_poly_render(void *p0, int x, int y)
|
||||
{
|
||||
jpainted *p = p0;
|
||||
p->paint(x, y, p->arg);
|
||||
}
|
||||
|
||||
/* jpainted type definition */
|
||||
static jwidget_poly type_jpainted = {
|
||||
.name = "jpainted",
|
||||
.csize = jpainted_poly_csize,
|
||||
.layout = NULL,
|
||||
.render = jpainted_poly_render,
|
||||
.event = NULL,
|
||||
.destroy = NULL,
|
||||
};
|
||||
|
||||
/* Type registration */
|
||||
__attribute__((constructor(1004)))
|
||||
static void j_register_jpainted(void)
|
||||
{
|
||||
jpainted_type_id = j_register_widget(&type_jpainted, "jwidget");
|
||||
}
|
203
src/jscene.c
Normal file
203
src/jscene.c
Normal file
|
@ -0,0 +1,203 @@
|
|||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <justui/jscene.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/drivers/keydev.h>
|
||||
#include <gint/clock.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
#include <gint/gint.h>
|
||||
#include <gint/drivers/t6k11.h>
|
||||
|
||||
/* Type identifier for jscene */
|
||||
static int jscene_type_id = -1;
|
||||
/* Events */
|
||||
uint16_t JSCENE_NONE;
|
||||
uint16_t JSCENE_PAINT;
|
||||
uint16_t JSCENE_KEY;
|
||||
|
||||
/* Keyboard transformation for inputs in a jscene */
|
||||
static int jscene_repeater(int key, GUNUSED int duration, int count)
|
||||
{
|
||||
if(key != KEY_LEFT && key != KEY_RIGHT && key != KEY_UP && key != KEY_DOWN)
|
||||
return -1;
|
||||
|
||||
return (count ? 40 : 400) * 1000;
|
||||
}
|
||||
static keydev_transform_t jscene_tr = {
|
||||
.enabled =
|
||||
KEYDEV_TR_DELAYED_SHIFT |
|
||||
KEYDEV_TR_INSTANT_SHIFT |
|
||||
KEYDEV_TR_DELAYED_ALPHA |
|
||||
KEYDEV_TR_INSTANT_ALPHA |
|
||||
KEYDEV_TR_REPEATS,
|
||||
.repeater = jscene_repeater,
|
||||
};
|
||||
|
||||
jscene *jscene_create(int x, int y, int w, int h, void *parent)
|
||||
{
|
||||
if(jscene_type_id < 0) return NULL;
|
||||
|
||||
jscene *s = malloc(sizeof *s);
|
||||
if(!s) return NULL;
|
||||
|
||||
jwidget_init(&s->widget, jscene_type_id, parent);
|
||||
jwidget_set_fixed_size(s, w, h);
|
||||
|
||||
s->x = x;
|
||||
s->y = y;
|
||||
|
||||
s->focus = NULL;
|
||||
s->queue_first = 0;
|
||||
s->queue_next = 0;
|
||||
s->lost_events = 0;
|
||||
|
||||
/* Prepare first layout/paint operation */
|
||||
s->widget.dirty = 1;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
jscene *jscene_create_fullscreen(void *parent)
|
||||
{
|
||||
return jscene_create(0, 0, DWIDTH, DHEIGHT, parent);
|
||||
}
|
||||
|
||||
void jscene_render(jscene *scene)
|
||||
{
|
||||
jwidget_layout(scene);
|
||||
jwidget_render(scene, scene->x, scene->y);
|
||||
}
|
||||
|
||||
//---
|
||||
// Event management
|
||||
//---
|
||||
|
||||
jevent jscene_read_event(jscene *s)
|
||||
{
|
||||
if(s->queue_first == s->queue_next)
|
||||
return (jevent){ .source = NULL, .type = JSCENE_NONE };
|
||||
|
||||
jevent e = s->queue[s->queue_first];
|
||||
s->queue_first = (s->queue_first + 1) % JSCENE_QUEUE_SIZE;
|
||||
return e;
|
||||
}
|
||||
|
||||
void jscene_queue_event(jscene *s, jevent e)
|
||||
{
|
||||
/* Prevent filling and overflowing the queue */
|
||||
int next = (s->queue_next + 1) % JSCENE_QUEUE_SIZE;
|
||||
if(next == s->queue_first)
|
||||
{
|
||||
s->lost_events++;
|
||||
return;
|
||||
}
|
||||
|
||||
s->queue[s->queue_next] = e;
|
||||
s->queue_next = next;
|
||||
}
|
||||
|
||||
//---
|
||||
// Keyboard focus and keyboard events
|
||||
//---
|
||||
|
||||
void *jscene_focused_widget(jscene *s)
|
||||
{
|
||||
return s->focus;
|
||||
}
|
||||
|
||||
void jscene_set_focused_widget(jscene *s, void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
|
||||
/* Check that (s) is an ancestor of (w) */
|
||||
if(w) for(jwidget *anc = w; anc != (jwidget *)s; anc = anc->parent) {
|
||||
if(anc == NULL) return;
|
||||
}
|
||||
|
||||
/* Focus out old focused widget */
|
||||
if(s->focus) jwidget_event(s->focus,(jevent){ .type = JWIDGET_FOCUS_OUT });
|
||||
|
||||
s->focus = w;
|
||||
|
||||
/* Focus in newly-selected widget */
|
||||
if(w) jwidget_event(w, (jevent){ .type = JWIDGET_FOCUS_IN });
|
||||
}
|
||||
|
||||
/* jscene_process_event(): Send an event to the focused widget */
|
||||
bool jscene_process_key_event(jscene *scene, key_event_t event)
|
||||
{
|
||||
jwidget *candidate = scene->focus;
|
||||
jevent e = { .type = JWIDGET_KEY, .key = event };
|
||||
|
||||
while(candidate) {
|
||||
if(jwidget_event(candidate, e)) return true;
|
||||
candidate = candidate->parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* jscene_run(): Run a scene's main loop */
|
||||
jevent jscene_run(jscene *s)
|
||||
{
|
||||
keydev_t *d = keydev_std();
|
||||
keydev_transform_t tr0 = keydev_transform(d);
|
||||
keydev_set_transform(d, jscene_tr);
|
||||
|
||||
jevent e;
|
||||
while(1) {
|
||||
/* Create repaint events (also handle relayout if needed) */
|
||||
if(jwidget_layout_dirty(s) || jwidget_needs_update(s)) {
|
||||
jscene_queue_event(s, (jevent){ .type = JSCENE_PAINT });
|
||||
}
|
||||
|
||||
/* Queued GUI events */
|
||||
e = jscene_read_event(s);
|
||||
if(e.type != JSCENE_NONE) break;
|
||||
|
||||
/* Queued keyboard events */
|
||||
key_event_t k = keydev_read(d);
|
||||
|
||||
if(k.type == KEYEV_DOWN && k.key == KEY_MENU && !k.shift && !k.alpha) {
|
||||
gint_osmenu();
|
||||
continue;
|
||||
}
|
||||
#ifdef FX9860G
|
||||
if(k.type == KEYEV_DOWN && k.key == KEY_OPTN && k.shift && !k.alpha) {
|
||||
t6k11_backlight(-1);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if(k.type != KEYEV_NONE && !jscene_process_key_event(s, k)) {
|
||||
e.type = JSCENE_KEY;
|
||||
e.key = k;
|
||||
break;
|
||||
}
|
||||
|
||||
sleep();
|
||||
}
|
||||
|
||||
keydev_set_transform(d, tr0);
|
||||
return e;
|
||||
}
|
||||
|
||||
/* jscene type definition */
|
||||
static jwidget_poly type_jscene = {
|
||||
.name = "jscene",
|
||||
.csize = NULL,
|
||||
.layout = NULL,
|
||||
.render = NULL,
|
||||
.event = NULL,
|
||||
.destroy = NULL,
|
||||
};
|
||||
|
||||
__attribute__((constructor(1001)))
|
||||
static void j_register_jscene(void)
|
||||
{
|
||||
jscene_type_id = j_register_widget(&type_jscene, "jwidget");
|
||||
JSCENE_NONE = j_register_event();
|
||||
JSCENE_PAINT = j_register_event();
|
||||
JSCENE_KEY = j_register_event();
|
||||
}
|
677
src/jwidget.c
Normal file
677
src/jwidget.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <justui/jscene.h>
|
||||
#include <justui/jevent.h>
|
||||
#include "jlayout_p.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
#include <gint/std/string.h>
|
||||
|
||||
#define WIDGET_TYPES_MAX 32
|
||||
|
||||
/* Polymorphic functions for jwidget */
|
||||
static jwidget_poly_render_t jwidget_poly_render;
|
||||
static jwidget_poly_csize_t jwidget_poly_csize;
|
||||
|
||||
/* jwidget type definition */
|
||||
static jwidget_poly type_jwidget = {
|
||||
.name = "jwidget",
|
||||
.csize = jwidget_poly_csize,
|
||||
.layout = NULL,
|
||||
.render = jwidget_poly_render,
|
||||
.event = NULL,
|
||||
.destroy = NULL,
|
||||
};
|
||||
|
||||
/* List of registered widget types */
|
||||
static jwidget_poly *widget_types[WIDGET_TYPES_MAX] = {
|
||||
&type_jwidget,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Events */
|
||||
uint16_t JWIDGET_KEY;
|
||||
uint16_t JWIDGET_FOCUS_IN;
|
||||
uint16_t JWIDGET_FOCUS_OUT;
|
||||
|
||||
//---
|
||||
// Polymorphic functions for widgets
|
||||
//---
|
||||
|
||||
static void jwidget_poly_render(void *w0, int x, int y)
|
||||
{
|
||||
J_CAST(w)
|
||||
jlayout_stack *l;
|
||||
|
||||
/* If there is a stack layout, render active child */
|
||||
if((l = jlayout_get_stack(w)) && l->active >= 0) {
|
||||
jwidget *child = w->children[l->active];
|
||||
if(child->visible) jwidget_render(child, x+child->x, y+child->y);
|
||||
}
|
||||
/* Otherwise, simply render all children */
|
||||
else {
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
jwidget *child = w->children[k];
|
||||
if(child->visible) jwidget_render(child, x+child->x, y+child->y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void jwidget_poly_csize(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
|
||||
/* The content box of this children is the union of the border boxes of the
|
||||
children. This function is called only for widgets without a layout, in
|
||||
which case the children must have their positions set. */
|
||||
w->w = 0;
|
||||
w->h = 0;
|
||||
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
jwidget *child = w->children[k];
|
||||
if(!child->visible) continue;
|
||||
jwidget_msize(child);
|
||||
|
||||
w->w = max(w->w, child->x + child->w);
|
||||
w->h = max(w->h, child->y + child->h);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Initialization
|
||||
//---
|
||||
|
||||
jwidget *jwidget_create(void *parent)
|
||||
{
|
||||
jwidget *w = malloc(sizeof *w);
|
||||
if(!w) return NULL;
|
||||
|
||||
/* Type ID 0 is for jwidget */
|
||||
jwidget_init(w, 0, parent);
|
||||
return w;
|
||||
}
|
||||
|
||||
void jwidget_init(jwidget *w, int type, void *parent)
|
||||
{
|
||||
w->parent = NULL;
|
||||
w->children = NULL;
|
||||
w->child_count = 0;
|
||||
w->child_alloc = 0;
|
||||
w->dirty = 1;
|
||||
w->visible = 1;
|
||||
|
||||
w->type = type;
|
||||
w->geometry = NULL;
|
||||
|
||||
w->x = 0;
|
||||
w->y = 0;
|
||||
w->w = 0;
|
||||
w->h = 0;
|
||||
w->min_w = 0;
|
||||
w->min_h = 0;
|
||||
w->max_w = 0x7fff;
|
||||
w->max_h = 0x7fff;
|
||||
|
||||
w->layout = J_LAYOUT_NONE;
|
||||
w->stretch_x = 0;
|
||||
w->stretch_y = 0;
|
||||
w->stretch_force = 0;
|
||||
|
||||
jwidget_set_parent(w, parent);
|
||||
}
|
||||
|
||||
void jwidget_destroy(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
jwidget_set_parent(w, NULL);
|
||||
|
||||
/* Run the custom destructor */
|
||||
jwidget_poly const *poly = widget_types[w->type];
|
||||
if(poly->destroy) poly->destroy(w);
|
||||
|
||||
for(int i = 0; i < w->child_count; i++) {
|
||||
/* This will prevent us from unregistering the child from w in the
|
||||
recursive call, which saves some time in pointer manipulation */
|
||||
w->children[i]->parent = NULL;
|
||||
jwidget_destroy(w->children[i]);
|
||||
}
|
||||
|
||||
if(w->children) free(w->children);
|
||||
if(w->geometry) free(w->geometry);
|
||||
free(w);
|
||||
}
|
||||
|
||||
//---
|
||||
// Ownership
|
||||
//---
|
||||
|
||||
void jwidget_set_parent(void *w0, void *parent0)
|
||||
{
|
||||
J_CAST(w, parent)
|
||||
if(w->parent == parent) return;
|
||||
|
||||
if(w->parent != NULL)
|
||||
jwidget_remove_child(w->parent, w);
|
||||
if(parent != NULL)
|
||||
jwidget_add_child(parent, w);
|
||||
}
|
||||
|
||||
void jwidget_add_child(void *w0, void *child0)
|
||||
{
|
||||
J_CAST(w, child)
|
||||
jwidget_insert_child(w, child, w->child_count);
|
||||
}
|
||||
|
||||
void jwidget_insert_child(void *w0, void *child0, int position)
|
||||
{
|
||||
J_CAST(w, child)
|
||||
if(child->parent == w) return;
|
||||
if(position < 0 || position > w->child_count) return;
|
||||
|
||||
/* Don't overflow the child_count and child_alloc fields! */
|
||||
if(w->child_count >= 256 - 4) return;
|
||||
|
||||
/* Make room for a new child if needed */
|
||||
if(w->child_count + 1 > w->child_alloc) {
|
||||
size_t new_size = (w->child_alloc + 4) * sizeof(jwidget *);
|
||||
jwidget **new_children = realloc(w->children, new_size);
|
||||
if(!new_children) return;
|
||||
|
||||
w->children = new_children;
|
||||
w->child_alloc += 4;
|
||||
}
|
||||
|
||||
/* Insert new child */
|
||||
for(int k = w->child_count; k > position; k--)
|
||||
w->children[k] = w->children[k - 1];
|
||||
|
||||
w->children[position] = child;
|
||||
w->child_count++;
|
||||
|
||||
/* Remove the existing parent at the last moment, in order to keep the tree
|
||||
in a safe state if allocations fail or parameters are invalid */
|
||||
if(child->parent != NULL)
|
||||
jwidget_remove_child(child->parent, child);
|
||||
|
||||
child->parent = w;
|
||||
|
||||
/* Update stack layouts to keep showing the same child */
|
||||
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active >= position) {
|
||||
w->layout_stack.active++;
|
||||
}
|
||||
/* Update stack layout to show the first child if none was visible yet */
|
||||
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active == -1) {
|
||||
w->layout_stack.active = 0;
|
||||
}
|
||||
|
||||
/* Force a later recomputation of the layout */
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_remove_child(void *w0, void *child0)
|
||||
{
|
||||
J_CAST(w, child)
|
||||
if(child->parent != w) return;
|
||||
|
||||
int write = 0;
|
||||
int index = -1;
|
||||
|
||||
/* Remove all occurrences of (child) from (w->children) */
|
||||
for(int read = 0; read < w->child_count; read++) {
|
||||
if(w->children[read] != child) {
|
||||
w->children[write++] = w->children[read];
|
||||
}
|
||||
else if(index < 0) {
|
||||
index = read;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the parent from the child */
|
||||
child->parent = NULL;
|
||||
|
||||
/* Update stack layouts to not show the removed child */
|
||||
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active == index) {
|
||||
w->layout_stack.active = -1;
|
||||
}
|
||||
|
||||
/* Force a later recomputation of the layout */
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
//---
|
||||
// Sizing and stretching
|
||||
//---
|
||||
|
||||
void jwidget_set_minimum_width(void *w0, int min_width)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->min_w = clamp(min_width, 0, 0x7fff);
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_set_minimum_height(void *w0, int min_height)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->min_h = clamp(min_height, 0, 0x7fff);
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_set_minimum_size(void *w, int min_width, int min_height)
|
||||
{
|
||||
jwidget_set_minimum_width(w, min_width);
|
||||
jwidget_set_minimum_height(w, min_height);
|
||||
}
|
||||
|
||||
void jwidget_set_maximum_width(void *w0, int max_width)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->max_w = clamp(max_width, 0, 0x7fff);
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_set_maximum_height(void *w0, int max_height)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->max_h = clamp(max_height, 0, 0x7fff);
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_set_maximum_size(void *w, int max_width, int max_height)
|
||||
{
|
||||
jwidget_set_maximum_width(w, max_width);
|
||||
jwidget_set_maximum_height(w, max_height);
|
||||
}
|
||||
|
||||
void jwidget_set_fixed_width(void *w, int width)
|
||||
{
|
||||
jwidget_set_minimum_width(w, width);
|
||||
jwidget_set_maximum_width(w, width);
|
||||
}
|
||||
|
||||
void jwidget_set_fixed_height(void *w, int height)
|
||||
{
|
||||
jwidget_set_minimum_height(w, height);
|
||||
jwidget_set_maximum_height(w, height);
|
||||
}
|
||||
|
||||
void jwidget_set_fixed_size(void *w, int width, int height)
|
||||
{
|
||||
jwidget_set_minimum_size(w, width, height);
|
||||
jwidget_set_maximum_size(w, width, height);
|
||||
}
|
||||
|
||||
void jwidget_set_stretch(void *w0, int stretch_x, int stretch_y, bool force)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->stretch_x = clamp(stretch_x, 0, 15);
|
||||
w->stretch_y = clamp(stretch_y, 0, 15);
|
||||
w->stretch_force = force;
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
//---
|
||||
// Geometry
|
||||
//---
|
||||
|
||||
static jwidget_geometry default_geometry = {
|
||||
.margins = { 0, 0, 0, 0 },
|
||||
.borders = { 0, 0, 0, 0 },
|
||||
.paddings = { 0, 0, 0, 0 },
|
||||
.border_color = C_NONE,
|
||||
.border_style = J_BORDER_NONE,
|
||||
.background_color = C_NONE,
|
||||
};
|
||||
|
||||
jwidget_geometry const *jwidget_geometry_r(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return (w->geometry == NULL) ? &default_geometry : w->geometry;
|
||||
}
|
||||
|
||||
jwidget_geometry *jwidget_geometry_rw(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
|
||||
/* Duplicate default geometry as a copy-on-write tactic to save memory */
|
||||
if(w->geometry == NULL) {
|
||||
w->geometry = malloc(sizeof *w->geometry);
|
||||
if(!w->geometry) return NULL;
|
||||
*w->geometry = default_geometry;
|
||||
}
|
||||
|
||||
/* Assume layout will need to be recomputed */
|
||||
w->dirty = 1;
|
||||
|
||||
return w->geometry;
|
||||
}
|
||||
|
||||
void jwidget_set_border(void *w, jwidget_border_style s, int width, int color)
|
||||
{
|
||||
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||||
|
||||
g->border_style = s;
|
||||
g->border_color = color;
|
||||
for(int i = 0; i < 4; i++) g->borders[i] = width;
|
||||
}
|
||||
|
||||
void jwidget_set_padding(void *w, int top, int right, int bottom, int left)
|
||||
{
|
||||
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||||
g->padding.top = top;
|
||||
g->padding.right = right;
|
||||
g->padding.bottom = bottom;
|
||||
g->padding.left = left;
|
||||
}
|
||||
|
||||
void jwidget_set_margin(void *w, int top, int right, int bottom, int left)
|
||||
{
|
||||
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||||
g->margin.top = top;
|
||||
g->margin.right = right;
|
||||
g->margin.bottom = bottom;
|
||||
g->margin.left = left;
|
||||
}
|
||||
|
||||
void jwidget_set_background(void *w, int color)
|
||||
{
|
||||
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||||
g->background_color = color;
|
||||
}
|
||||
|
||||
//---
|
||||
// Layout
|
||||
//---
|
||||
|
||||
void jlayout_set_manual(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
w->layout = J_LAYOUT_NONE;
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
void jwidget_msize(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
int t = w->layout;
|
||||
|
||||
/* Size of contents */
|
||||
if(t == J_LAYOUT_NONE) {
|
||||
jwidget_poly const *poly = widget_types[w->type];
|
||||
if(poly->csize) poly->csize(w);
|
||||
}
|
||||
|
||||
else if(t == J_LAYOUT_HBOX || t == J_LAYOUT_VBOX)
|
||||
jlayout_box_csize(w);
|
||||
|
||||
else if(t == J_LAYOUT_STACK)
|
||||
jlayout_stack_csize(w);
|
||||
|
||||
else if(t == J_LAYOUT_GRID)
|
||||
jlayout_grid_csize(w);
|
||||
|
||||
/* Add the size of the geometry */
|
||||
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||||
|
||||
w->w += g->margin.left + g->border.left + g->padding.left;
|
||||
w->w += g->margin.right + g->border.right + g->padding.right;
|
||||
w->h += g->margin.top + g->border.top + g->padding.top;
|
||||
w->h += g->margin.bottom + g->border.bottom + g->padding.bottom;
|
||||
|
||||
w->dirty = 1;
|
||||
}
|
||||
|
||||
bool jwidget_layout_dirty(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(w->dirty) return true;
|
||||
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
if(!w->children[k]->visible) continue;
|
||||
if(jwidget_layout_dirty(w->children[k])) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool jwidget_visible(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return w->visible;
|
||||
}
|
||||
|
||||
void jwidget_set_visible(void *w0, bool visible)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(w->visible == visible) return;
|
||||
|
||||
w->visible = visible;
|
||||
if(w->parent) w->parent->dirty = 1;
|
||||
}
|
||||
|
||||
bool jwidget_needs_update(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(w->update) return true;
|
||||
|
||||
jlayout_stack *l = jlayout_get_stack(w);
|
||||
|
||||
/* Ignore invisible children, because their update bits are not reset after
|
||||
a repaint, resulting in infinite REPAINT events */
|
||||
for(int k = 0; k < w->child_count; k++) {
|
||||
if(l && l->active != k) continue;
|
||||
if(!w->children[k]->visible) continue;
|
||||
if(jwidget_needs_update(w->children[k])) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Apply layout recursively on this widget and its children */
|
||||
static void jwidget_layout_apply(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(!w->visible) return;
|
||||
int t = w->layout;
|
||||
|
||||
if(t == J_LAYOUT_NONE) {
|
||||
jwidget_poly const *poly = widget_types[w->type];
|
||||
if(poly->layout) poly->layout(w);
|
||||
}
|
||||
else if(t == J_LAYOUT_HBOX || t == J_LAYOUT_VBOX) {
|
||||
jlayout_box_apply(w);
|
||||
}
|
||||
else if(t == J_LAYOUT_STACK) {
|
||||
jlayout_stack_apply(w);
|
||||
}
|
||||
else if(t == J_LAYOUT_GRID) {
|
||||
jlayout_grid_apply(w);
|
||||
}
|
||||
|
||||
/* The layout is now up-to-date and will not be recomputed until a widget
|
||||
in the hierarchy requires it */
|
||||
w->dirty = 0;
|
||||
|
||||
for(int k = 0; k < w->child_count; k++)
|
||||
jwidget_layout_apply(w->children[k]);
|
||||
}
|
||||
|
||||
void jwidget_layout(void *root0)
|
||||
{
|
||||
J_CAST(root)
|
||||
if(!jwidget_layout_dirty(root)) return;
|
||||
|
||||
/* Phase 1: Compute the natural margin size of every widget (bottom-up) */
|
||||
jwidget_msize(root);
|
||||
|
||||
/* Decide on the size of the root; first make sure it's acceptable */
|
||||
root->w = clamp(root->w, root->min_w, root->max_w);
|
||||
root->h = clamp(root->h, root->min_h, root->max_h);
|
||||
/* Now if there is stretch and a maximum size, stretch */
|
||||
if(root->stretch_x && root->max_w != 0x7fff)
|
||||
root->w = root->max_w;
|
||||
if(root->stretch_y && root->max_h != 0x7fff)
|
||||
root->h = root->max_h;
|
||||
|
||||
/* Phase 2: Distribute space recursively (top-down) */
|
||||
jwidget_layout_apply(root);
|
||||
}
|
||||
|
||||
int jwidget_content_width(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||||
|
||||
return w->w - g->margin.left - g->border.left - g->padding.left
|
||||
- g->margin.right - g->border.right - g->padding.right;
|
||||
}
|
||||
|
||||
int jwidget_content_height(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||||
|
||||
return w->h - g->margin.top - g->border.top - g->padding.top
|
||||
- g->margin.bottom - g->border.bottom - g->padding.bottom;
|
||||
}
|
||||
|
||||
int jwidget_full_width(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return w->w;
|
||||
}
|
||||
|
||||
int jwidget_full_height(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return w->h;
|
||||
}
|
||||
|
||||
//---
|
||||
// Rendering
|
||||
//---
|
||||
|
||||
void jwidget_render(void *w0, int x, int y)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(!w->visible) return;
|
||||
|
||||
/* Render widget border */
|
||||
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||||
jdirs b = g->border;
|
||||
int color = g->border_color;
|
||||
|
||||
int cw = jwidget_content_width(w);
|
||||
int ch = jwidget_content_height(w);
|
||||
|
||||
int x1 = x + g->margin.left;
|
||||
int y1 = y + g->margin.top;
|
||||
int x2 = x1 + b.left + g->padding.left + cw + g->padding.right;
|
||||
int y2 = y1 + b.top + g->padding.top + ch + g->padding.bottom;
|
||||
|
||||
if(g->border_style == J_BORDER_NONE || color == C_NONE) {
|
||||
}
|
||||
else if(g->border_style == J_BORDER_SOLID) {
|
||||
drect(x1, y1, x2 + b.right - 1, y1 + b.top - 1, color);
|
||||
drect(x1, y2, x2 + b.right - 1, y2 + b.bottom - 1, color);
|
||||
|
||||
drect(x1, y1 + b.top, x1 + b.left - 1, y2 - 1, color);
|
||||
drect(x2, y1 + b.top, x2 + b.right - 1, y2 - 1, color);
|
||||
}
|
||||
/* TODO: jwidget_render(): More border types */
|
||||
|
||||
if(g->background_color != C_NONE) {
|
||||
drect(x1 + b.left, y1 + b.top, x2, y2, g->background_color);
|
||||
}
|
||||
|
||||
/* Call the polymorphic render function at the top-left content point */
|
||||
x += g->margin.left + b.left + g->padding.left;
|
||||
y += g->margin.top + b.top + g->padding.top;
|
||||
|
||||
jwidget_poly const *poly = widget_types[w->type];
|
||||
if(poly->render) poly->render(w, x, y);
|
||||
|
||||
w->update = 0;
|
||||
}
|
||||
|
||||
//---
|
||||
// Event management
|
||||
//---
|
||||
|
||||
bool jwidget_event(void *w0, jevent e)
|
||||
{
|
||||
J_CAST(w)
|
||||
jwidget_poly const *poly = widget_types[w->type];
|
||||
if(poly->event) return poly->event(w, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
void jwidget_emit(void *w0, jevent e)
|
||||
{
|
||||
J_CAST(w)
|
||||
if(!w) return;
|
||||
|
||||
if(e.source == NULL) e.source = w;
|
||||
|
||||
if(!strcmp(jwidget_type(w), "jscene")) {
|
||||
jscene_queue_event((jscene *)w, e);
|
||||
}
|
||||
else {
|
||||
jwidget_emit(w->parent, e);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Extension API
|
||||
//---
|
||||
|
||||
char const *jwidget_type(void *w0)
|
||||
{
|
||||
J_CAST(w)
|
||||
return widget_types[w->type]->name;
|
||||
}
|
||||
|
||||
int j_register_widget(jwidget_poly *poly, char const *inherits)
|
||||
{
|
||||
/* Resolve inheritance */
|
||||
if(inherits) {
|
||||
jwidget_poly const *base = NULL;
|
||||
|
||||
for(int i = 0; i < WIDGET_TYPES_MAX && !base; i++) {
|
||||
if(widget_types[i] && !strcmp(widget_types[i]->name, inherits))
|
||||
base = widget_types[i];
|
||||
}
|
||||
|
||||
if(!base) return -1;
|
||||
|
||||
if(!poly->csize) poly->csize = base->csize;
|
||||
if(!poly->layout) poly->layout = base->layout;
|
||||
if(!poly->render) poly->render = base->render;
|
||||
if(!poly->event) poly->event = base->event;
|
||||
if(!poly->destroy) poly->destroy = base->destroy;
|
||||
}
|
||||
|
||||
for(int i = 0; i < WIDGET_TYPES_MAX; i++) {
|
||||
if(widget_types[i] == NULL) {
|
||||
widget_types[i] = poly;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int j_register_event(void)
|
||||
{
|
||||
static int event_id = 0;
|
||||
event_id++;
|
||||
return event_id;
|
||||
}
|
||||
|
||||
__attribute__((constructor(1000)))
|
||||
static void j_register_jwidget(void)
|
||||
{
|
||||
JWIDGET_KEY = j_register_event();
|
||||
JWIDGET_FOCUS_IN = j_register_event();
|
||||
JWIDGET_FOCUS_OUT = j_register_event();
|
||||
}
|
56
src/keymap.c
Normal file
56
src/keymap.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "util.h"
|
||||
#include <gint/keyboard.h>
|
||||
|
||||
static int key_id(int keycode)
|
||||
{
|
||||
uint col = (keycode & 0x0f) - 1;
|
||||
uint row = 9 - ((keycode & 0xf0) >> 4);
|
||||
|
||||
if(col > 5 || row > 8) return -1;
|
||||
return 6 * row + col;
|
||||
}
|
||||
|
||||
static uint8_t map_flat[30] = {
|
||||
0, 0, '(', ')', ',', '=',
|
||||
'7', '8', '9', 0, 0, 0,
|
||||
'4', '5', '6', '*', '/', 0,
|
||||
'1', '2', '3', '+', '-', 0,
|
||||
'0', '.', 'e', '-', 0, 0,
|
||||
};
|
||||
static uint8_t map_alpha[36] = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l',
|
||||
'm', 'n', 'o', 0, 0, 0,
|
||||
'p', 'q', 'r', 's', 't', 0,
|
||||
'u', 'v', 'w', 'x', 'y', 0,
|
||||
'z', ' ', '"', 0, 0, 0,
|
||||
};
|
||||
|
||||
uint32_t keymap_translate(int key, bool shift, bool alpha)
|
||||
{
|
||||
int id = key_id(key);
|
||||
if(id < 0) return 0;
|
||||
|
||||
if(!shift && !alpha) {
|
||||
/* The first 4 rows have no useful characters */
|
||||
return (id < 24) ? 0 : map_flat[id - 24];
|
||||
}
|
||||
if(shift && !alpha) {
|
||||
if(key == KEY_MUL) return '{';
|
||||
if(key == KEY_DIV) return '}';
|
||||
if(key == KEY_ADD) return '[';
|
||||
if(key == KEY_SUB) return ']';
|
||||
if(key == KEY_DOT) return '=';
|
||||
if(key == KEY_EXP) return 0x3c0; // 'π'
|
||||
}
|
||||
if(!shift && alpha) {
|
||||
/* The first 3 rows have no useful characters */
|
||||
return (id < 18) ? 0 : map_alpha[id - 18];
|
||||
}
|
||||
if(shift && alpha) {
|
||||
int c = keymap_translate(key, false, true);
|
||||
return (c >= 'a' && c <= 'z') ? (c & ~0x20) : c;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
23
src/util.h
Normal file
23
src/util.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//---
|
||||
// JustUI.util: Header-level utilities that cannot be exposed to users
|
||||
//---
|
||||
|
||||
#ifndef _J_UTIL
|
||||
#define _J_UTIL
|
||||
|
||||
#include <justui/defs.h>
|
||||
|
||||
/* Clamp a value between two ends. */
|
||||
__attribute__((always_inline))
|
||||
static inline int clamp(int value, int min, int max)
|
||||
{
|
||||
/* Mark the branches as unlikely, that might help */
|
||||
if(__builtin_expect(value < min, 0)) return min;
|
||||
if(__builtin_expect(value > max, 0)) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Code point for a character input */
|
||||
uint32_t keymap_translate(int key, bool shift, bool alpha);
|
||||
|
||||
#endif /* _J_UTIL */
|
65
src/vec.c
Normal file
65
src/vec.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include <justui/p/vec.h>
|
||||
#include <gint/std/stdlib.h>
|
||||
|
||||
void vec_init(vec_t *v, size_t elsize)
|
||||
{
|
||||
v->data = NULL;
|
||||
v->size = 0;
|
||||
v->free = 0;
|
||||
v->elsize = elsize;
|
||||
}
|
||||
|
||||
void vec_clear(vec_t *v)
|
||||
{
|
||||
free(v->data);
|
||||
v->data = NULL;
|
||||
v->size = 0;
|
||||
v->free = 0;
|
||||
/* Leave v->elsize for potential future reuse */
|
||||
}
|
||||
|
||||
bool vec_add(vec_t *v, size_t n)
|
||||
{
|
||||
if(n > v->free) {
|
||||
/* Allocate either size/2 new elements or 4 elements */
|
||||
size_t ext = max(v->size / 2, 4);
|
||||
/* Make sure that no more than 255 free elements will remain */
|
||||
if(v->size + v->free + ext > n + 255)
|
||||
ext = (n + 255) - (v->size + v->free);
|
||||
|
||||
size_t newsize = v->size + v->free + ext;
|
||||
void *newdata = realloc(v->data, newsize * v->elsize);
|
||||
if(!newdata) return false;
|
||||
|
||||
v->data = newdata;
|
||||
v->free = newsize - n;
|
||||
}
|
||||
else {
|
||||
v->free -= n;
|
||||
}
|
||||
|
||||
v->size += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vec_remove(vec_t *v, size_t n)
|
||||
{
|
||||
n = min(n, v->size);
|
||||
|
||||
/* Make sure that the vector is at least half-full and that no more than
|
||||
255 free elements remain */
|
||||
if(v->size - n <= (v->size + v->free) / 2 || v->free + n > 255) {
|
||||
size_t newsize = v->size - n;
|
||||
void *newdata = realloc(v->data, newsize * v->elsize);
|
||||
if(!newdata) return false;
|
||||
|
||||
v->data = newdata;
|
||||
v->free = 0;
|
||||
}
|
||||
else {
|
||||
v->free += n;
|
||||
}
|
||||
|
||||
v->size -= n;
|
||||
return true;
|
||||
}
|
Loading…
Reference in a new issue