~yerinalexey/hare-libui

0743d70d1bca500e1d71991630d92997f1bf29e5 — Rosie K Languet 1 year, 1 month ago 28f87a3
Bind nonscrolling uiArea for key events and fill drawing

Adds third tab to demo.

Co-Authored-By: Alexey Yerin <yyp@disroot.org>
M cmd/demo/main.ha => cmd/demo/main.ha +71 -0
@@ 2,6 2,7 @@ use fmt;
use os::exec;
use os;
use ui;
use ui::area;

export fn main() void = {
	match (ui::init()) {


@@ 41,10 42,22 @@ export fn main() void = {
		ui::box_append(box, btn, false);
		yield box;
	};
	const tab3 = {
		let ah1 = area::handler_new();

		ah1.KeyEvent = &handle_space;
		ah1.Draw = &draw_rectangle;
		const a1 = area::nonscrolling_area_new(&ah1);

		const box = ui::box_new(ui::orientation::VERTICAL);
		ui::box_append(box, a1, true);
		yield box;
	};

	const tab = ui::tab_new();
	ui::tab_append(tab, "Register", tab1);
	ui::tab_append(tab, "Sign in", tab2);
	ui::tab_append(tab, "Area demo: press space", tab3);

	const win = ui::window_new("Hare + libui = <3", 800, 640, true);
	ui::window_set_child(win, tab);


@@ 54,6 67,64 @@ export fn main() void = {
	ui::mainloop();
};

type block_state = enum {
	NONE,
	TOP,
	BOTTOM,
};
let this_block_state: block_state = block_state::NONE;

fn handle_space(self: *area::handler, a: *area::area, ke: *area::key_event) int = {
	const r = match (area::get_key(ke)) {
	case area::ext_key =>
		return 0;
	case let r: rune =>
		yield r;
	};

	if (r != ' ' || ke.Up != 0) {
		return 0;
	};

	switch (this_block_state) {
	case block_state::NONE =>
		this_block_state = block_state::TOP;
	case block_state::TOP =>
		this_block_state = block_state::BOTTOM;
	case block_state::BOTTOM =>
		this_block_state = block_state::NONE;
	};
	area::queue_redraw_all(a);
	return 1;
};

fn draw_rectangle(self: *area::handler, a: *area::area, dp: *area::draw_params) void = {
	const top_left = switch (this_block_state) {
	case block_state::NONE =>
		return;
	case block_state::TOP =>
		yield area::coord {
			x = 0.0,
			y = 0.0,
		};
	case block_state::BOTTOM =>
		yield area::coord {
			x = 0.0,
			y = dp.NonscrollingArea.Height / 2.0,
		};
	};

	const path = area::draw_path_new(area::draw_fill_mode::Alternate);
	defer area::draw_path_free(path);
	area::add_rectangle(path,
		top_left,
		dp.NonscrollingArea.Width / 2.0,
		dp.NonscrollingArea.Height / 2.0);
	area::draw_path_end(path);

	area::draw_fill(dp.Context, path, area::color_from_hex(0x00069f, 1.0));
};

fn window_close(win: *ui::window, data: nullable *void) void = {
	ui::quit();
};

A ui/area/area.ha => ui/area/area.ha +44 -0
@@ 0,0 1,44 @@
use fmt;

export type area = void;

// Creates a new area, registering callbacks.
export fn nonscrolling_area_new(handler: *handler) *area = {
	return c_uiNewArea(handler);
};

// ToDo: uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height);

// Container for callbacks for area events.
export type handler = struct {
	Draw: *fn(self: *handler, a: *area, dp: *draw_params) void,
	MouseEvent: *fn(self: *handler, a: *area, me: *mouse_event) void,
	MouseCrossed: *fn(self: *handler, a: *area, left: int) void,
	DragBroken: *fn(self: *handler, a: *area) void,
	KeyEvent: *fn(self: *handler, a: *area, ke: *key_event) int,
};

// Creates a new area handler with no-op callbacks.
export fn handler_new() handler = {
	return handler {
		Draw = &draw_noop,
		KeyEvent = &key_event_noop,
		MouseEvent = &mouse_event_noop,
		MouseCrossed = &mouse_crossed_noop,
		DragBroken = &drag_broken_noop,
	};
};

// Coordinates within an [[area]].
export type coord = struct {
	x: f64,
	y: f64,
};

fn draw_noop(self: *handler, a: *area, dp: *draw_params) void = void;
fn mouse_event_noop(self: *handler, a: *area, me: *mouse_event) void = void;
fn mouse_crossed_noop(self: *handler, a: *area, left: int) void = void;
fn drag_broken_noop(self: *handler, a: *area) void = void;
fn key_event_noop(self: *handler, a: *area, ke: *key_event) int = 0;

@symbol("uiNewArea") fn c_uiNewArea(_: *handler) *area;

A ui/area/color.ha => ui/area/color.ha +18 -0
@@ 0,0 1,18 @@
export type color = struct {
	R: f64,
	G: f64,
	B: f64,
	A: f64,
};

// Creates a new color from a 0xRRGGBB hex code (1 byte per channel). Ignores
// the most-significant byte.
export fn color_from_hex(hex: u32, alpha: f64) color = {
	hex &= 0x00FFFFFF;
	return color {
		R = (((hex >> 16) & 0xFF): f64 / 255.0),
		G = (((hex >> 08) & 0xFF): f64 / 255.0),
		B = (((hex >> 00) & 0xFF): f64 / 255.0),
		A = alpha,
	};
};

A ui/area/draw.ha => ui/area/draw.ha +103 -0
@@ 0,0 1,103 @@
// Queues an area for redrawing on the main thread, from a [[handler]] callback.
export fn queue_redraw_all(a: *area) void = {
	c_uiAreaQueueRedrawAll(a);
};

// Parameters for a [[handler]].Draw event.
export type draw_params = struct {
	Context: *draw_context,

	NonscrollingArea: struct {
		Width: f64,
		Height: f64,
	},

	Clip: coord,
	ClipWidth: f64,
	ClipHeight: f64,
};

// A drawing surface that you can draw to. Used to issue draw commands from a
// [[handler]].Draw callback.
export type draw_context = void;

export type draw_brush = (solid_brush | gradient_brush); // ToDo: image_brush

export type gradient_brush = (linear_gradient_brush | radial_gradient_brush);

export type solid_brush = color;

export type linear_gradient_brush = struct {
	start: coord,
	end: coord,
	stops: []grad_stop,
};

export type radial_gradient_brush = struct {
	start: coord,
	outer_circle_center: coord,
	radius: f64,
	stops: []grad_stop,
};

export type grad_stop = struct {
	pos: f64,
	color: color,
};

export type draw_fill_mode = enum uint {
	Winding,
	Alternate,
};

// Fills a [[draw_path]] using a [[draw_brush]], from a [[handler]].Draw
// callback.
export fn draw_fill(context: *draw_context, p: *draw_path, b: draw_brush) void = {
	c_uiDrawFill(context, p, draw_brush_to_c(b));
};

fn draw_brush_to_c(b: draw_brush) *c_draw_brush = {
	let cb = alloc(c_draw_brush { ... });
	match (b) {
	case let sb: solid_brush =>
		cb.brush_type = c_draw_brush_type::Solid;
		cb.solid_brush_color = sb: color;
	case let lb: linear_gradient_brush =>
		cb.brush_type = c_draw_brush_type::LinearGradient;
		cb.start = lb.start;
		cb.end_or_out_ctr = lb.end;
		cb.grad_stop_arr = lb.stops: *[*]grad_stop: *grad_stop;
		cb.num_grad_stops = len(lb.stops);
	case let rb: radial_gradient_brush =>
		cb.brush_type = c_draw_brush_type::RadialGradient;
		cb.start = rb.start;
		cb.end_or_out_ctr = rb.outer_circle_center;
		cb.grad_rad = rb.radius;
		cb.grad_stop_arr = rb.stops: *[*]grad_stop: *grad_stop;
		cb.num_grad_stops = len(rb.stops);
	};
	return cb;
};

type c_draw_brush = struct {
	brush_type: c_draw_brush_type,

	solid_brush_color: color,

	start: coord,
	end_or_out_ctr: coord,
	grad_rad: f64,

	grad_stop_arr: *grad_stop,
	num_grad_stops: size,
};

type c_draw_brush_type = enum uint {
	Solid,
	LinearGradient,
	RadialGradient,
	Image, //ToDo
};

@symbol("uiDrawFill") fn c_uiDrawFill(_: *draw_context, _: *draw_path, _: *c_draw_brush) void;
@symbol("uiAreaQueueRedrawAll") fn c_uiAreaQueueRedrawAll(_: *area) void;

A ui/area/draw_path.ha => ui/area/draw_path.ha +40 -0
@@ 0,0 1,40 @@
// A geometric path in a drawing context. This is the basic unit of drawing;
// all drawing operations consist of:
// 1. forming a path
// 2. forming figures in that path
// 3. ending the path (prepares it for drawing)
// 4. issuing the draw command
// 5. freeing the path
//
// The only figure supported currently is the rectangle.
export type draw_path = void;

// Creates a new draw path. A path must be freed using [[draw_path_free]] after
// use.
export fn draw_path_new(mode: draw_fill_mode) *draw_path = {
	return c_uiDrawNewPath(mode);
};

// Frees a draw path.
export fn draw_path_free(p: *draw_path) void = {
	c_uiDrawFreePath(p);
};

// Prepares a draw path for drawing. You cannot add figures to a path that has
// been ended. You cannot draw with a path that has not been ended.
export fn draw_path_end(p: *draw_path) void = {
	c_uiDrawPathEnd(p);
};

// Creates a new figure in the path that consists entirely of a rectangle whose
// top-left corner is at the given point and whose size is the given size. The
// rectangle is a closed figure; you must either start a new figure or end the
// path after calling this.
export fn add_rectangle(path: *draw_path, top_left: coord, width: f64, height: f64) void = {
	c_uiDrawPathAddRectangle(path, top_left.x, top_left.y, width, height);
};

@symbol("uiDrawNewPath") fn c_uiDrawNewPath(_: draw_fill_mode) *draw_path;
@symbol("uiDrawFreePath") fn c_uiDrawFreePath(_: *draw_path) void;
@symbol("uiDrawPathAddRectangle") fn c_uiDrawPathAddRectangle(_: *draw_path, _: f64, _: f64, _: f64, _: f64) void;
@symbol("uiDrawPathEnd") fn c_uiDrawPathEnd(_: *draw_path) void;

A ui/area/key.ha => ui/area/key.ha +67 -0
@@ 0,0 1,67 @@
export type key_event = struct {
	Key: u8,
	ExtKey: ext_key,
	Modifier: key_modifier,
	Modifiers: key_modifier,
	Up: int,
};

// Returns the event's key as a rune for printable symbols, or [[ext_key]] for
// special keys.
export fn get_key(ke: *key_event) (rune | ext_key) = {
	if (ke.Key == 0) {
		return ke.ExtKey;
	};
	return ke.Key: u32: rune;
};

// Extended keys; not representable as ASCII/rune.
export type ext_key = enum uint {
	Escape = 1,
	Insert,	// equivalent to "Help" on Apple keyboards
	Delete,
	Home,
	End,
	PageUp,
	PageDown,
	Up,
	Down,
	Left,
	Right,
	F1,	// F1..F12 are guaranteed to be consecutive
	F2,
	F3,
	F4,
	F5,
	F6,
	F7,
	F8,
	F9,
	F10,
	F11,
	F12,
	N0, // numpad keys; independent of Num Lock state
	N1, // N0..N9 are guaranteed to be consecutive
	N2,
	N3,
	N4,
	N5,
	N6,
	N7,
	N8,
	N9,
	NDot,
	NEnter,
	NAdd,
	NSubtract,
	NMultiply,
	NDivide,
};

// Modifier keys.
export type key_modifier = enum uint {
	Ctrl = 1 << 0,
	Alt = 1 << 1,
	Shift = 1 << 2,
	Super = 1 << 3,
};

A ui/area/mouse.ha => ui/area/mouse.ha +1 -0
@@ 0,0 1,1 @@
export type mouse_event = void; // ToDo

M ui/controls.ha => ui/controls.ha +4 -2
@@ 1,6 1,8 @@
use ui::area;

// A UI control.
export type control = (*window | *box | *button | *checkbox | *entry | *label |
	*tab);
export type control = (*window | *area::area | *box | *button | *checkbox |
	*entry | *label | *tab);

fn control_ptr(c: control) *void = {
	// All controls have the same representation, so all of them will