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