mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-29 13:03:37 +01:00
9d30377d90
This commit rewrites the entire device management layer of fxlink on the libusb side, providing new abstractions that support live/async device management, including communication. This system is put to use in a new TUI interactive mode (fxlink -t) which can run in the background, connects to calculators automatically without interfering with file transfer tools, and is much more detailed in its interface than the previous interactive mode (fxlink -i). The TUI mode will also soon be extended to support sending data.
452 lines
13 KiB
C
452 lines
13 KiB
C
//---------------------------------------------------------------------------//
|
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include <fxlink/tui/layout.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
typedef unsigned int uint;
|
|
|
|
//---
|
|
// Flexbox-like allocation algorithm (copy/pasted from JustUI)
|
|
//
|
|
// 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 = fminf(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 = fminf(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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static int compare_ids(void const *ptr1, void const *ptr2)
|
|
{
|
|
exp_t const *e1 = ptr1;
|
|
exp_t const *e2 = ptr2;
|
|
return e1->id - e2->id;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Sort back by IDs for final ordering */
|
|
qsort(elements, n, sizeof *elements, compare_ids);
|
|
}
|
|
|
|
//---
|
|
// TUI layout
|
|
//---
|
|
|
|
static struct fxlink_TUI_box *mkbox(void)
|
|
{
|
|
struct fxlink_TUI_box *b = calloc(1, sizeof *b);
|
|
if(b) {
|
|
b->max_w = 0xffff;
|
|
b->max_h = 0xffff;
|
|
b->stretch_x = 1;
|
|
b->stretch_y = 1;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_window(char const *title, WINDOW **w)
|
|
{
|
|
struct fxlink_TUI_box *b = mkbox();
|
|
if(b) {
|
|
b->type = FXLINK_TUI_BOX_WINDOW;
|
|
b->window.title = title;
|
|
b->window.win = w;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
static struct fxlink_TUI_box *mkcontainer(int type,
|
|
struct fxlink_TUI_box *child, va_list args)
|
|
{
|
|
struct fxlink_TUI_box *b = mkbox();
|
|
if(b) {
|
|
b->type = type;
|
|
int i = 0;
|
|
|
|
while(child && i < FXLINK_TUI_BOX_MAXSIZE) {
|
|
b->children[i++] = child;
|
|
child = va_arg(args, struct fxlink_TUI_box *);
|
|
}
|
|
}
|
|
return b;
|
|
}
|
|
|
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_vertical(
|
|
struct fxlink_TUI_box *child1, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, child1);
|
|
return mkcontainer(FXLINK_TUI_BOX_VERTICAL, child1, args);
|
|
va_end(args);
|
|
}
|
|
|
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_horizontal(
|
|
struct fxlink_TUI_box *child1, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, child1);
|
|
return mkcontainer(FXLINK_TUI_BOX_HORIZONTAL, child1, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void fxlink_TUI_box_minsize(struct fxlink_TUI_box *box, int min_w, int min_h)
|
|
{
|
|
box->min_w = min_w;
|
|
box->min_h = min_h;
|
|
}
|
|
|
|
void fxlink_TUI_box_maxsize(struct fxlink_TUI_box *box, int max_w, int max_h)
|
|
{
|
|
box->max_w = max_w;
|
|
box->max_h = max_h;
|
|
}
|
|
|
|
void fxlink_TUI_box_stretch(struct fxlink_TUI_box *box, int stretch_x,
|
|
int stretch_y, bool force)
|
|
{
|
|
box->stretch_x = stretch_x;
|
|
box->stretch_y = stretch_y;
|
|
box->stretch_force = force;
|
|
}
|
|
|
|
void fxlink_TUI_box_print(FILE *fp, struct fxlink_TUI_box const *b, int level)
|
|
{
|
|
for(int i = 0; i < level * 4; i++)
|
|
fputc(' ', fp);
|
|
|
|
fprintf(fp, "type=");
|
|
if(b->type == FXLINK_TUI_BOX_WINDOW)
|
|
fprintf(fp, "WINDOW '%s'", b->window.title);
|
|
if(b->type == FXLINK_TUI_BOX_VERTICAL)
|
|
fprintf(fp, "VERTICAL");
|
|
if(b->type == FXLINK_TUI_BOX_HORIZONTAL)
|
|
fprintf(fp, "HORIZONTAL");
|
|
|
|
fprintf(fp, " x=%d y=%d w=", b->x, b->y);
|
|
|
|
if(b->min_w > 0)
|
|
fprintf(fp, "(%d)<", b->min_w);
|
|
fprintf(fp, "%d", b->w);
|
|
if(b->max_w < 0xffff)
|
|
fprintf(fp, "<(%d)", b->max_w);
|
|
|
|
fprintf(fp, " h=");
|
|
|
|
if(b->min_h > 0)
|
|
fprintf(fp, "(%d)<", b->min_h);
|
|
fprintf(fp, "%d", b->h);
|
|
if(b->max_h < 0xffff)
|
|
fprintf(fp, "<(%d)", b->max_h);
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
if(b->type != FXLINK_TUI_BOX_WINDOW) {
|
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && b->children[i]; i++)
|
|
fxlink_TUI_box_print(fp, b->children[i], level+1);
|
|
}
|
|
}
|
|
|
|
static void box_do_layout(struct fxlink_TUI_box *box)
|
|
{
|
|
if(box->type == FXLINK_TUI_BOX_WINDOW)
|
|
return;
|
|
|
|
int horiz = (box->type == FXLINK_TUI_BOX_HORIZONTAL);
|
|
size_t child_count = 0;
|
|
while(child_count < FXLINK_TUI_BOX_MAXSIZE && box->children[child_count])
|
|
child_count++;
|
|
int spacing = 1;
|
|
|
|
/* Content width and height */
|
|
int cw = box->w;
|
|
int ch = box->h;
|
|
/* Allocatable width and height (which excludes spacing) */
|
|
int total_spacing = (child_count - 1) * 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 = child_count;
|
|
exp_t elements[n];
|
|
|
|
for(size_t i = 0; i < child_count; i++) {
|
|
struct fxlink_TUI_box *child = box->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(i > 0)
|
|
length += 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);
|
|
}
|
|
}
|
|
|
|
/* 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];
|
|
struct fxlink_TUI_box *child = box->children[e->id];
|
|
|
|
if(horiz)
|
|
child->w += e->allocated;
|
|
else
|
|
child->h += e->allocated;
|
|
}
|
|
|
|
/* Position everyone */
|
|
int position = 0;
|
|
|
|
for(size_t i = 0; i < n; i++) {
|
|
exp_t *e = &elements[i];
|
|
struct fxlink_TUI_box *child = box->children[e->id];
|
|
|
|
if(horiz) {
|
|
child->x = box->x + position;
|
|
child->y = box->y + (ch - child->h) / 2;
|
|
position += child->w + spacing;
|
|
}
|
|
else {
|
|
child->x = box->x + (cw - child->w) / 2;
|
|
child->y = box->y + position;
|
|
position += child->h + spacing;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && box->children[i]; i++)
|
|
box_do_layout(box->children[i]);
|
|
}
|
|
|
|
void fxlink_TUI_box_layout(struct fxlink_TUI_box *b,
|
|
int x, int y, int w, int h)
|
|
{
|
|
b->x = x + 1;
|
|
b->y = y + 1;
|
|
b->w = w - 2;
|
|
b->h = h - 2;
|
|
return box_do_layout(b);
|
|
}
|
|
|
|
static bool box_apply(struct fxlink_TUI_box *box,
|
|
int rx, int ry, int rw, int rh)
|
|
{
|
|
if(box->type == FXLINK_TUI_BOX_WINDOW) {
|
|
if(!box->window.win)
|
|
return true;
|
|
|
|
/* Ensure window is of non-zero size and in-bounds */
|
|
int x = clamp(box->x, rx, rx + rw - 1);
|
|
int y = clamp(box->y, ry, ry + rh - 1);
|
|
int w = clamp(box->w, 1, rw - (x - rx));
|
|
int h = clamp(box->h, 1, rh - (y - ry));
|
|
|
|
if(!*box->window.win) {
|
|
*box->window.win = newwin(h, w, y, x);
|
|
}
|
|
else {
|
|
wresize(*box->window.win, h, w);
|
|
mvwin(*box->window.win, y, x);
|
|
}
|
|
return (*box->window.win != NULL);
|
|
}
|
|
|
|
bool success = true;
|
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && box->children[i]; i++)
|
|
success = success && box_apply(box->children[i], rx, ry, rw, rh);
|
|
return success;
|
|
}
|
|
|
|
bool fxlink_TUI_apply_layout(struct fxlink_TUI_box *root)
|
|
{
|
|
return box_apply(root, root->x, root->y, root->w, root->h);
|
|
}
|