feat(lang): block value requires no trailing ; (Rust-style)
A block's value is now its last statement ONLY when that statement is a trailing expression with no `;`. A trailing `;` discards the value, leaving the block void. This makes value-vs-statement explicit and lets the compiler reject "this block was supposed to produce a value". Compiler: - Parser records `Block.produces_value` (last stmt is a no-`;` trailing expression) + `Block.discarded_semi` (the `;` that discarded a value), via `expectSemicolonAfter`. A trailing expression before `}` may now omit its `;` (previously a parse error). Match-arm and else-arm bodies are built value-producing regardless of the arm `;` (arms are exempt — the `;` is an arm terminator). - Lowering: `lowerBlockValue` / the block-expr path / `inferExprType` respect `produces_value`. A value-position block that discards its value is a hard error (`lowerValueBody` for function bodies; the value-context `.block` path for if/else branches, `catch` bodies, value bindings, match arms). Pure-failable `-> !` bodies (value rides the error channel) and a value-if whose branches are void are handled without false errors. - `defer`/`onfail` cleanup bodies lower as statements (void), so a trailing `;` there is fine. Migration (behavior-preserving — output unchanged): - stdlib + ~210 examples: dropped the trailing `;` on value-position last expressions. `format` now ends with an explicit `#insert "return result;"` (it relied on `#insert`-as-block-value, which `;` discards). - Two `main :: () -> s32` examples that relied on the old silent default-return got an explicit trailing `0`. - Rejection snapshots 0412 / 1013 regenerated (their quoted source lines lost a `;`); the diagnostics themselves are unchanged. Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041 (rejection); 3 parser unit tests. Filed issue 0066 (pre-existing match-arm negated-literal phi-width quirk, surfaced not caused here). Gates: zig build, zig build test, run_examples.sh -> 343 passed, cross_compile.sh -> 7 passed (also refreshed its stale example names).
This commit is contained in:
@@ -9,14 +9,14 @@ Lerpable :: protocol #inline {
|
||||
|
||||
// --- Easing Functions ---
|
||||
|
||||
ease_linear :: (t: f32) -> f32 { t; }
|
||||
ease_in_quad :: (t: f32) -> f32 { t * t; }
|
||||
ease_out_quad :: (t: f32) -> f32 { t * (2.0 - t); }
|
||||
ease_linear :: (t: f32) -> f32 { t }
|
||||
ease_in_quad :: (t: f32) -> f32 { t * t }
|
||||
ease_out_quad :: (t: f32) -> f32 { t * (2.0 - t) }
|
||||
ease_in_out_quad :: (t: f32) -> f32 {
|
||||
if t < 0.5 then 2.0 * t * t
|
||||
else -1.0 + (4.0 - 2.0 * t) * t;
|
||||
else -1.0 + (4.0 - 2.0 * t) * t
|
||||
}
|
||||
ease_out_cubic :: (t: f32) -> f32 { u := t - 1.0; u * u * u + 1.0; }
|
||||
ease_out_cubic :: (t: f32) -> f32 { u := t - 1.0; u * u * u + 1.0 }
|
||||
|
||||
// --- AnimatedFloat — duration-based ---
|
||||
|
||||
@@ -38,7 +38,7 @@ AnimatedFloat :: struct {
|
||||
duration = 0.0,
|
||||
easing = null,
|
||||
active = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
animate_to :: (self: *AnimatedFloat, target: f32, dur: f32, ease: Closure(f32) -> f32) {
|
||||
@@ -54,7 +54,7 @@ AnimatedFloat :: struct {
|
||||
if !self.active { return; }
|
||||
self.elapsed += dt;
|
||||
t := clamp(self.elapsed / self.duration, 0.0, 1.0);
|
||||
eased := if ease := self.easing { ease(t); } else { t; };
|
||||
eased := if ease := self.easing { ease(t) } else { t };
|
||||
self.current = self.from + (self.to - self.from) * eased;
|
||||
if t >= 1.0 {
|
||||
self.current = self.to;
|
||||
@@ -83,7 +83,7 @@ SpringFloat :: struct {
|
||||
damping = 20.0,
|
||||
mass = 1.0,
|
||||
threshold = 0.01
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
snappy :: (value: f32) -> SpringFloat {
|
||||
@@ -95,7 +95,7 @@ SpringFloat :: struct {
|
||||
damping = 25.0,
|
||||
mass = 1.0,
|
||||
threshold = 0.01
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
tick :: (self: *SpringFloat, dt: f32) {
|
||||
@@ -109,7 +109,7 @@ SpringFloat :: struct {
|
||||
|
||||
is_settled :: (self: *SpringFloat) -> bool {
|
||||
abs(self.current - self.target) < self.threshold
|
||||
and abs(self.velocity) < self.threshold;
|
||||
and abs(self.velocity) < self.threshold
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ Animated :: struct ($T: Lerpable) {
|
||||
elapsed = 0.0,
|
||||
duration = 0.0,
|
||||
active = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Jump immediately to value (no animation). Used to avoid animating from zero on first layout.
|
||||
@@ -163,5 +163,5 @@ Animated :: struct ($T: Lerpable) {
|
||||
}
|
||||
}
|
||||
|
||||
is_animating :: (self: *Animated(T)) -> bool { self.active; }
|
||||
is_animating :: (self: *Animated(T)) -> bool { self.active }
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ ButtonStyle :: struct {
|
||||
pressed_bg = Color.rgb(0, 80, 180),
|
||||
corner_radius = 6.0,
|
||||
padding = EdgeInsets.symmetric(16.0, 8.0)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ impl View for Button {
|
||||
Size.{
|
||||
width = text_size.width + self.style.padding.horizontal(),
|
||||
height = text_size.height + self.style.padding.vertical()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
layout :: (self: *Button, bounds: Frame) {}
|
||||
@@ -86,6 +86,6 @@ impl View for Button {
|
||||
}
|
||||
}
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ dock_zone_to_alignment :: (zone: DockZone) -> ?Alignment {
|
||||
}
|
||||
|
||||
dock_zone_should_fill :: (zone: DockZone) -> bool {
|
||||
zone == .fill;
|
||||
zone == .fill
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -169,7 +169,7 @@ DockInteraction :: struct {
|
||||
|
||||
get_animated_size :: (self: *DockInteraction, index: s64) -> Size {
|
||||
if index >= self.child_count { return Size.zero(); }
|
||||
(@self.anim_sizes.items[index]).current;
|
||||
(@self.anim_sizes.items[index]).current
|
||||
}
|
||||
|
||||
tick_animations :: (self: *DockInteraction, dt: f32) {
|
||||
@@ -184,7 +184,7 @@ DockInteraction :: struct {
|
||||
get_hovered_dock_zone :: (self: *DockInteraction) -> ?DockZone {
|
||||
if self.hovered_zone < 0 { return null; }
|
||||
// Map ordinal back to DockZone
|
||||
cast(DockZone) self.hovered_zone;
|
||||
cast(DockZone) self.hovered_zone
|
||||
}
|
||||
|
||||
set_hovered_dock_zone :: (self: *DockInteraction, zone: ?DockZone) {
|
||||
@@ -242,7 +242,7 @@ find_hovered_zone :: (bounds: Frame, pos: Point, hint_size: f32, enable_corners:
|
||||
if expanded.contains(pos) { return zone; }
|
||||
i += 1;
|
||||
}
|
||||
null;
|
||||
null
|
||||
}
|
||||
|
||||
calculate_origin :: (bounds: Frame, child_size: Size, alignment: Alignment) -> Point {
|
||||
@@ -262,7 +262,7 @@ calculate_origin :: (bounds: Frame, child_size: Size, alignment: Alignment) -> P
|
||||
y = bounds.origin.y + bounds.size.height - child_size.height;
|
||||
}
|
||||
|
||||
Point.{ x = x, y = y };
|
||||
Point.{ x = x, y = y }
|
||||
}
|
||||
|
||||
get_size_proposal_for_alignment :: (alignment: Alignment, bounds_size: Size, is_fill: bool) -> ProposedSize {
|
||||
@@ -280,7 +280,7 @@ get_size_proposal_for_alignment :: (alignment: Alignment, bounds_size: Size, is_
|
||||
}
|
||||
}
|
||||
// Center or corners: natural size
|
||||
ProposedSize.flexible();
|
||||
ProposedSize.flexible()
|
||||
}
|
||||
|
||||
get_final_size_for_alignment :: (alignment: Alignment, child_size: Size, bounds_size: Size, is_fill: bool) -> Size {
|
||||
@@ -299,7 +299,7 @@ get_final_size_for_alignment :: (alignment: Alignment, child_size: Size, bounds_
|
||||
}
|
||||
}
|
||||
// Center or corners: natural size
|
||||
child_size;
|
||||
child_size
|
||||
}
|
||||
|
||||
draw_zone_indicator :: (ctx: *RenderContext, frame: Frame, zone: DockZone, color: Color) {
|
||||
@@ -385,15 +385,15 @@ DockPanel :: struct {
|
||||
header_height = DockPanel.DEFAULT_HEADER_H,
|
||||
dock_interaction = null, // set by Dock.add_panel
|
||||
panel_index = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for DockPanel {
|
||||
size_that_fits :: (self: *DockPanel, proposal: ProposedSize) -> Size {
|
||||
content_size := self.child.view.size_that_fits(ProposedSize.{ width = proposal.width, height = null });
|
||||
w := if pw := proposal.width { min(content_size.width, pw); } else { content_size.width; };
|
||||
Size.{ width = w, height = content_size.height + self.header_height };
|
||||
w := if pw := proposal.width { min(content_size.width, pw) } else { content_size.width };
|
||||
Size.{ width = w, height = content_size.height + self.header_height }
|
||||
}
|
||||
|
||||
layout :: (self: *DockPanel, bounds: Frame) {
|
||||
@@ -446,7 +446,7 @@ impl View for DockPanel {
|
||||
}
|
||||
|
||||
// Forward to child content
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +484,7 @@ Dock :: struct {
|
||||
d.preview_color = Color.rgba(77, 153, 255, 64);
|
||||
d.enable_corners = true;
|
||||
d.on_dock = null;
|
||||
d;
|
||||
d
|
||||
}
|
||||
|
||||
add_panel :: (self: *Dock, panel: DockPanel) {
|
||||
@@ -508,7 +508,7 @@ impl View for Dock {
|
||||
Size.{
|
||||
width = proposal.width ?? 800.0,
|
||||
height = proposal.height ?? 600.0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
layout :: (self: *Dock, bounds: Frame) {
|
||||
@@ -687,6 +687,6 @@ impl View for Dock {
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ event_position :: (e: *Event) -> ?Point {
|
||||
case .mouse_moved: (d) { return d.position; }
|
||||
case .mouse_wheel: (d) { return d.position; }
|
||||
}
|
||||
null;
|
||||
null
|
||||
}
|
||||
|
||||
// Map a platform (SDL) keycode to the neutral `Keycode`. Only the keys the app
|
||||
@@ -72,7 +72,7 @@ keycode_from_sdl :: (k: SDL_Keycode) -> Keycode {
|
||||
case .up: return .up;
|
||||
case .down: return .down;
|
||||
}
|
||||
.unknown;
|
||||
.unknown
|
||||
}
|
||||
|
||||
// Translate SDL_Event → our Event type
|
||||
@@ -129,5 +129,5 @@ translate_sdl_event :: (sdl: *SDL_Event) -> Event {
|
||||
});
|
||||
}
|
||||
}
|
||||
.none;
|
||||
.none
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ measure_text :: (text: string, font_size: f32) -> Size {
|
||||
scale := font_size / 16.0;
|
||||
return Size.{ width = xx text.len * 8.0 * scale, height = font_size };
|
||||
}
|
||||
g_font.measure_text(text, font_size);
|
||||
g_font.measure_text(text, font_size)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ TapGesture :: struct {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ DragGesture :: struct {
|
||||
location = self.current_location,
|
||||
start_location = self.start_location,
|
||||
translation = self.current_location.sub(self.start_location)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
handle_event :: (self: *DragGesture, event: *Event, frame: Frame) -> bool {
|
||||
@@ -123,6 +123,6 @@ DragGesture :: struct {
|
||||
self.phase = .possible;
|
||||
}
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,16 +28,16 @@ GlyphEntry :: struct {
|
||||
// Quantize font size to half-point increments to limit cache entries.
|
||||
// e.g., 13.0 -> 26, 13.5 -> 27, 14.0 -> 28
|
||||
quantize_size :: (font_size: f32) -> u16 {
|
||||
xx (font_size * 2.0 + 0.5);
|
||||
xx (font_size * 2.0 + 0.5)
|
||||
}
|
||||
|
||||
dequantize_size :: (q: u16) -> f32 {
|
||||
xx q / 2.0;
|
||||
xx q / 2.0
|
||||
}
|
||||
|
||||
// Pack (glyph_index, size_quantized) into a single u32 for fast comparison
|
||||
make_glyph_key :: (glyph_index: u16, size_quantized: u16) -> u32 {
|
||||
(xx glyph_index << 16) | xx size_quantized;
|
||||
(xx glyph_index << 16) | xx size_quantized
|
||||
}
|
||||
|
||||
// Shaped glyph — output of text shaping (positioned glyph with index)
|
||||
@@ -54,7 +54,7 @@ is_ascii :: (text: string) -> bool {
|
||||
if text[i] >= 128 { return false; }
|
||||
i += 1;
|
||||
}
|
||||
true;
|
||||
true
|
||||
}
|
||||
|
||||
// kbts constants (C enum values)
|
||||
@@ -480,7 +480,7 @@ GlyphCache :: struct {
|
||||
}
|
||||
|
||||
// No space
|
||||
PackResult.{ x = 0 - 1, y = 0 - 1 };
|
||||
PackResult.{ x = 0 - 1, y = 0 - 1 }
|
||||
}
|
||||
|
||||
// Grow the atlas by doubling dimensions
|
||||
@@ -547,18 +547,18 @@ GlyphCache :: struct {
|
||||
|
||||
// Get the scale factor for a logical font size
|
||||
scale_for_size :: (self: *GlyphCache, font_size: f32) -> f32 {
|
||||
stbtt_ScaleForPixelHeight(self.font_info, font_size);
|
||||
stbtt_ScaleForPixelHeight(self.font_info, font_size)
|
||||
}
|
||||
|
||||
// Get scaled ascent for a logical font size
|
||||
get_ascent :: (self: *GlyphCache, font_size: f32) -> f32 {
|
||||
self.ascent * self.scale_for_size(font_size);
|
||||
self.ascent * self.scale_for_size(font_size)
|
||||
}
|
||||
|
||||
// Get scaled line height for a logical font size
|
||||
get_line_height :: (self: *GlyphCache, font_size: f32) -> f32 {
|
||||
s := self.scale_for_size(font_size);
|
||||
(self.ascent - self.descent + self.line_gap) * s;
|
||||
(self.ascent - self.descent + self.line_gap) * s
|
||||
}
|
||||
|
||||
// Shape text into positioned glyphs.
|
||||
@@ -654,6 +654,6 @@ GlyphCache :: struct {
|
||||
width += self.shaped_buf.items[i].advance;
|
||||
i += 1;
|
||||
}
|
||||
Size.{ width = width, height = self.get_line_height(font_size) };
|
||||
Size.{ width = width, height = self.get_line_height(font_size) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ impl View for ImageView {
|
||||
// Maintain aspect ratio: fit within proposal
|
||||
aspect := self.width / self.height;
|
||||
if pw / ph > aspect {
|
||||
Size.{ width = ph * aspect, height = ph };
|
||||
Size.{ width = ph * aspect, height = ph }
|
||||
} else {
|
||||
Size.{ width = pw, height = pw / aspect };
|
||||
Size.{ width = pw, height = pw / aspect }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ impl View for ImageView {
|
||||
}
|
||||
|
||||
handle_event :: (self: *ImageView, event: *Event, frame: Frame) -> bool {
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ Label :: struct {
|
||||
text = text,
|
||||
font_size = 14.0,
|
||||
color = COLOR_WHITE
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for Label {
|
||||
size_that_fits :: (self: *Label, proposal: ProposedSize) -> Size {
|
||||
measure_text(self.text, self.font_size);
|
||||
measure_text(self.text, self.font_size)
|
||||
}
|
||||
|
||||
layout :: (self: *Label, bounds: Frame) {
|
||||
@@ -33,6 +33,6 @@ impl View for Label {
|
||||
}
|
||||
|
||||
handle_event :: (self: *Label, event: *Event, frame: Frame) -> bool {
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ measure_vstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing:
|
||||
total_height = total_height + spacing * xx (n - 1);
|
||||
|
||||
result_width := min(proposal.width ?? max_width, max_width);
|
||||
Size.{ width = result_width, height = total_height };
|
||||
Size.{ width = result_width, height = total_height }
|
||||
}
|
||||
|
||||
measure_hstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing: f32) -> Size {
|
||||
@@ -129,7 +129,7 @@ measure_hstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing:
|
||||
total_width = total_width + spacing * xx (n - 1);
|
||||
|
||||
result_height := min(proposal.height ?? max_height, max_height);
|
||||
Size.{ width = total_width, height = result_height };
|
||||
Size.{ width = total_width, height = result_height }
|
||||
}
|
||||
|
||||
measure_zstack :: (children: *List(ViewChild), proposal: ProposedSize) -> Size {
|
||||
@@ -148,5 +148,5 @@ measure_zstack :: (children: *List(ViewChild), proposal: ProposedSize) -> Size {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Size.{ width = max_width, height = max_height };
|
||||
Size.{ width = max_width, height = max_height }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ impl View for PaddingModifier {
|
||||
Size.{
|
||||
width = child_size.width + self.insets.horizontal(),
|
||||
height = child_size.height + self.insets.vertical()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
layout :: (self: *PaddingModifier, bounds: Frame) {
|
||||
@@ -37,7 +37,7 @@ impl View for PaddingModifier {
|
||||
}
|
||||
|
||||
handle_event :: (self: *PaddingModifier, event: *Event, frame: Frame) -> bool {
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ impl View for FrameModifier {
|
||||
Size.{
|
||||
width = self.width ?? child_size.width,
|
||||
height = self.height ?? child_size.height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
layout :: (self: *FrameModifier, bounds: Frame) {
|
||||
@@ -78,7 +78,7 @@ impl View for FrameModifier {
|
||||
}
|
||||
|
||||
handle_event :: (self: *FrameModifier, event: *Event, frame: Frame) -> bool {
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ BackgroundModifier :: struct {
|
||||
|
||||
impl View for BackgroundModifier {
|
||||
size_that_fits :: (self: *BackgroundModifier, proposal: ProposedSize) -> Size {
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *BackgroundModifier, bounds: Frame) {
|
||||
@@ -110,7 +110,7 @@ impl View for BackgroundModifier {
|
||||
}
|
||||
|
||||
handle_event :: (self: *BackgroundModifier, event: *Event, frame: Frame) -> bool {
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ OpacityModifier :: struct {
|
||||
|
||||
impl View for OpacityModifier {
|
||||
size_that_fits :: (self: *OpacityModifier, proposal: ProposedSize) -> Size {
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *OpacityModifier, bounds: Frame) {
|
||||
@@ -139,7 +139,7 @@ impl View for OpacityModifier {
|
||||
}
|
||||
|
||||
handle_event :: (self: *OpacityModifier, event: *Event, frame: Frame) -> bool {
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ ClipModifier :: struct {
|
||||
|
||||
impl View for ClipModifier {
|
||||
size_that_fits :: (self: *ClipModifier, proposal: ProposedSize) -> Size {
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *ClipModifier, bounds: Frame) {
|
||||
@@ -167,7 +167,7 @@ impl View for ClipModifier {
|
||||
}
|
||||
|
||||
handle_event :: (self: *ClipModifier, event: *Event, frame: Frame) -> bool {
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ HiddenModifier :: struct {
|
||||
impl View for HiddenModifier {
|
||||
size_that_fits :: (self: *HiddenModifier, proposal: ProposedSize) -> Size {
|
||||
if self.is_hidden { return Size.zero(); }
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *HiddenModifier, bounds: Frame) {
|
||||
@@ -197,7 +197,7 @@ impl View for HiddenModifier {
|
||||
|
||||
handle_event :: (self: *HiddenModifier, event: *Event, frame: Frame) -> bool {
|
||||
if self.is_hidden { return false; }
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ TapGestureModifier :: struct {
|
||||
|
||||
impl View for TapGestureModifier {
|
||||
size_that_fits :: (self: *TapGestureModifier, proposal: ProposedSize) -> Size {
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *TapGestureModifier, bounds: Frame) {
|
||||
@@ -224,7 +224,7 @@ impl View for TapGestureModifier {
|
||||
|
||||
handle_event :: (self: *TapGestureModifier, event: *Event, frame: Frame) -> bool {
|
||||
if self.gesture.handle_event(event, frame) { return true; }
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ DragGestureModifier :: struct {
|
||||
|
||||
impl View for DragGestureModifier {
|
||||
size_that_fits :: (self: *DragGestureModifier, proposal: ProposedSize) -> Size {
|
||||
self.child.view.size_that_fits(proposal);
|
||||
self.child.view.size_that_fits(proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *DragGestureModifier, bounds: Frame) {
|
||||
@@ -251,46 +251,46 @@ impl View for DragGestureModifier {
|
||||
|
||||
handle_event :: (self: *DragGestureModifier, event: *Event, frame: Frame) -> bool {
|
||||
if self.gesture.handle_event(event, frame) { return true; }
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Convenience functions ---
|
||||
|
||||
padding :: (view: View, insets: EdgeInsets) -> PaddingModifier {
|
||||
PaddingModifier.{ child = .{ view = view }, insets = insets };
|
||||
PaddingModifier.{ child = .{ view = view }, insets = insets }
|
||||
}
|
||||
|
||||
fixed_frame :: (view: View, width: ?f32, height: ?f32) -> FrameModifier {
|
||||
FrameModifier.{ child = .{ view = view }, width = width, height = height };
|
||||
FrameModifier.{ child = .{ view = view }, width = width, height = height }
|
||||
}
|
||||
|
||||
background :: (view: View, color: Color, corner_radius: f32) -> BackgroundModifier {
|
||||
BackgroundModifier.{ child = .{ view = view }, color = color, corner_radius = corner_radius };
|
||||
BackgroundModifier.{ child = .{ view = view }, color = color, corner_radius = corner_radius }
|
||||
}
|
||||
|
||||
with_opacity :: (view: View, alpha: f32) -> OpacityModifier {
|
||||
OpacityModifier.{ child = .{ view = view }, alpha = alpha };
|
||||
OpacityModifier.{ child = .{ view = view }, alpha = alpha }
|
||||
}
|
||||
|
||||
clip :: (view: View, corner_radius: f32) -> ClipModifier {
|
||||
ClipModifier.{ child = .{ view = view }, corner_radius = corner_radius };
|
||||
ClipModifier.{ child = .{ view = view }, corner_radius = corner_radius }
|
||||
}
|
||||
|
||||
hidden :: (view: View, is_hidden: bool) -> HiddenModifier {
|
||||
HiddenModifier.{ child = .{ view = view }, is_hidden = is_hidden };
|
||||
HiddenModifier.{ child = .{ view = view }, is_hidden = is_hidden }
|
||||
}
|
||||
|
||||
on_tap :: (view: View, handler: Closure()) -> TapGestureModifier {
|
||||
TapGestureModifier.{
|
||||
child = .{ view = view },
|
||||
gesture = TapGesture.{ count = 1, on_tap = handler }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
on_drag :: (view: View, on_changed: ?Closure(DragValue), on_ended: ?Closure(DragValue)) -> DragGestureModifier {
|
||||
DragGestureModifier.{
|
||||
child = .{ view = view },
|
||||
gesture = DragGesture.{ min_distance = 10.0, on_changed = on_changed, on_ended = on_ended }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ UIPipeline :: struct {
|
||||
// Process a single event through the view tree
|
||||
dispatch_event :: (self: *UIPipeline, event: *Event) -> bool {
|
||||
if self.has_root == false { return false; }
|
||||
self.root.view.handle_event(event, self.root.computed_frame);
|
||||
self.root.view.handle_event(event, self.root.computed_frame)
|
||||
}
|
||||
|
||||
// Run one frame: layout → render → commit
|
||||
|
||||
@@ -43,7 +43,7 @@ RenderTree :: struct {
|
||||
generation: s64;
|
||||
|
||||
init :: () -> RenderTree {
|
||||
RenderTree.{ generation = 0 };
|
||||
RenderTree.{ generation = 0 }
|
||||
}
|
||||
|
||||
clear :: (self: *RenderTree) {
|
||||
@@ -54,7 +54,7 @@ RenderTree :: struct {
|
||||
add :: (self: *RenderTree, node: RenderNode) -> s64 {
|
||||
idx := self.nodes.len;
|
||||
self.nodes.append(node);
|
||||
idx;
|
||||
idx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ RenderContext :: struct {
|
||||
clip_depth = 0,
|
||||
opacity = 1.0,
|
||||
depth = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
add_rect :: (self: *RenderContext, frame: Frame, fill: Color) {
|
||||
|
||||
@@ -413,7 +413,7 @@ create_white_texture :: () -> u32 {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, @pixel);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_NEAREST);
|
||||
tex;
|
||||
tex
|
||||
}
|
||||
|
||||
// --- UI Shaders ---
|
||||
|
||||
@@ -51,7 +51,7 @@ impl View for ScrollView {
|
||||
Size.{
|
||||
width = proposal.width ?? 200.0,
|
||||
height = proposal.height ?? 200.0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
layout :: (self: *ScrollView, bounds: Frame) {
|
||||
@@ -144,6 +144,6 @@ impl View for ScrollView {
|
||||
return self.child.view.handle_event(event, self.child.computed_frame);
|
||||
}
|
||||
}
|
||||
self.child.view.handle_event(event, self.child.computed_frame);
|
||||
self.child.view.handle_event(event, self.child.computed_frame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ VStack :: struct {
|
||||
|
||||
impl View for VStack {
|
||||
size_that_fits :: (self: *VStack, proposal: ProposedSize) -> Size {
|
||||
measure_vstack(@self.children, proposal, self.spacing);
|
||||
measure_vstack(@self.children, proposal, self.spacing)
|
||||
}
|
||||
|
||||
layout :: (self: *VStack, bounds: Frame) {
|
||||
@@ -44,7 +44,7 @@ impl View for VStack {
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ HStack :: struct {
|
||||
|
||||
impl View for HStack {
|
||||
size_that_fits :: (self: *HStack, proposal: ProposedSize) -> Size {
|
||||
measure_hstack(@self.children, proposal, self.spacing);
|
||||
measure_hstack(@self.children, proposal, self.spacing)
|
||||
}
|
||||
|
||||
layout :: (self: *HStack, bounds: Frame) {
|
||||
@@ -85,7 +85,7 @@ impl View for HStack {
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ ZStack :: struct {
|
||||
|
||||
impl View for ZStack {
|
||||
size_that_fits :: (self: *ZStack, proposal: ProposedSize) -> Size {
|
||||
measure_zstack(@self.children, proposal);
|
||||
measure_zstack(@self.children, proposal)
|
||||
}
|
||||
|
||||
layout :: (self: *ZStack, bounds: Frame) {
|
||||
@@ -127,7 +127,7 @@ impl View for ZStack {
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ impl View for Spacer {
|
||||
size_that_fits :: (self: *Spacer, proposal: ProposedSize) -> Size {
|
||||
w := proposal.width ?? self.min_length;
|
||||
h := proposal.height ?? self.min_length;
|
||||
Size.{ width = max(w, self.min_length), height = max(h, self.min_length) };
|
||||
Size.{ width = max(w, self.min_length), height = max(h, self.min_length) }
|
||||
}
|
||||
|
||||
layout :: (self: *Spacer, bounds: Frame) {}
|
||||
render :: (self: *Spacer, ctx: *RenderContext, frame: Frame) {}
|
||||
handle_event :: (self: *Spacer, event: *Event, frame: Frame) -> bool { false; }
|
||||
handle_event :: (self: *Spacer, event: *Event, frame: Frame) -> bool { false }
|
||||
}
|
||||
|
||||
// Rect — simple colored rectangle view
|
||||
@@ -164,7 +164,7 @@ impl View for RectView {
|
||||
size_that_fits :: (self: *RectView, proposal: ProposedSize) -> Size {
|
||||
w := proposal.width ?? self.preferred_width;
|
||||
h := proposal.height ?? self.preferred_height;
|
||||
Size.{ width = w, height = h };
|
||||
Size.{ width = w, height = h }
|
||||
}
|
||||
|
||||
layout :: (self: *RectView, bounds: Frame) {}
|
||||
@@ -177,5 +177,5 @@ impl View for RectView {
|
||||
}
|
||||
}
|
||||
|
||||
handle_event :: (self: *RectView, event: *Event, frame: Frame) -> bool { false; }
|
||||
handle_event :: (self: *RectView, event: *Event, frame: Frame) -> bool { false }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
State :: struct ($T: Type) {
|
||||
ptr: *T;
|
||||
|
||||
get :: (self: State(T)) -> T { self.ptr.*; }
|
||||
get :: (self: State(T)) -> T { self.ptr.* }
|
||||
|
||||
set :: (self: State(T), val: T) { self.ptr.* = val; }
|
||||
}
|
||||
@@ -52,7 +52,7 @@ StateStore :: struct {
|
||||
size = size_of(T),
|
||||
generation = self.current_generation
|
||||
}, self.parent_allocator);
|
||||
State(T).{ ptr = xx data };
|
||||
State(T).{ ptr = xx data }
|
||||
}
|
||||
|
||||
next_frame :: (self: *StateStore) {
|
||||
|
||||
@@ -24,7 +24,7 @@ impl View for StatsPanel {
|
||||
fps_size := measure_text("FPS: 0000", StatsPanel.VALUE_SIZE);
|
||||
w := max(title_size.width, fps_size.width) + StatsPanel.PADDING * 2.0;
|
||||
h := title_size.height + StatsPanel.LINE_SPACING + fps_size.height + StatsPanel.PADDING * 2.0;
|
||||
Size.{ width = w, height = h };
|
||||
Size.{ width = w, height = h }
|
||||
}
|
||||
|
||||
layout :: (self: *StatsPanel, bounds: Frame) {}
|
||||
@@ -58,6 +58,6 @@ impl View for StatsPanel {
|
||||
}
|
||||
|
||||
handle_event :: (self: *StatsPanel, event: *Event, frame: Frame) -> bool {
|
||||
false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,21 @@ Point :: struct {
|
||||
zero :: () -> Point => .{ x = 0.0, y = 0.0 };
|
||||
|
||||
add :: (self: Point, b: Point) -> Point {
|
||||
Point.{ x = self.x + b.x, y = self.y + b.y };
|
||||
Point.{ x = self.x + b.x, y = self.y + b.y }
|
||||
}
|
||||
|
||||
sub :: (self: Point, b: Point) -> Point {
|
||||
Point.{ x = self.x - b.x, y = self.y - b.y };
|
||||
Point.{ x = self.x - b.x, y = self.y - b.y }
|
||||
}
|
||||
|
||||
scale :: (self: Point, s: f32) -> Point {
|
||||
Point.{ x = self.x * s, y = self.y * s };
|
||||
Point.{ x = self.x * s, y = self.y * s }
|
||||
}
|
||||
|
||||
distance :: (self: Point, b: Point) -> f32 {
|
||||
dx := self.x - b.x;
|
||||
dy := self.y - b.y;
|
||||
sqrt(dx * dx + dy * dy);
|
||||
sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Size :: struct {
|
||||
zero :: () -> Size => .{ width = 0.0, height = 0.0 };
|
||||
|
||||
contains :: (self: Size, point: Point) -> bool {
|
||||
point.x >= 0.0 and point.x <= self.width and point.y >= 0.0 and point.y <= self.height;
|
||||
point.x >= 0.0 and point.x <= self.width and point.y >= 0.0 and point.y <= self.height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,20 +39,20 @@ Frame :: struct {
|
||||
origin: Point;
|
||||
size: Size;
|
||||
|
||||
zero :: () -> Frame { Frame.{ origin = Point.zero(), size = Size.zero() }; }
|
||||
zero :: () -> Frame { Frame.{ origin = Point.zero(), size = Size.zero() } }
|
||||
|
||||
make :: (x: f32, y: f32, w: f32, h: f32) -> Frame {
|
||||
Frame.{ origin = Point.{ x = x, y = y }, size = Size.{ width = w, height = h } };
|
||||
Frame.{ origin = Point.{ x = x, y = y }, size = Size.{ width = w, height = h } }
|
||||
}
|
||||
|
||||
max_x :: (self: Frame) -> f32 { self.origin.x + self.size.width; }
|
||||
max_y :: (self: Frame) -> f32 { self.origin.y + self.size.height; }
|
||||
mid_x :: (self: Frame) -> f32 { self.origin.x + self.size.width * 0.5; }
|
||||
mid_y :: (self: Frame) -> f32 { self.origin.y + self.size.height * 0.5; }
|
||||
max_x :: (self: Frame) -> f32 { self.origin.x + self.size.width }
|
||||
max_y :: (self: Frame) -> f32 { self.origin.y + self.size.height }
|
||||
mid_x :: (self: Frame) -> f32 { self.origin.x + self.size.width * 0.5 }
|
||||
mid_y :: (self: Frame) -> f32 { self.origin.y + self.size.height * 0.5 }
|
||||
|
||||
contains :: (self: Frame, point: Point) -> bool {
|
||||
point.x >= self.origin.x and point.x <= self.max_x()
|
||||
and point.y >= self.origin.y and point.y <= self.max_y();
|
||||
and point.y >= self.origin.y and point.y <= self.max_y()
|
||||
}
|
||||
|
||||
intersection :: (self: Frame, other: Frame) -> Frame {
|
||||
@@ -62,7 +62,7 @@ Frame :: struct {
|
||||
y2 := min(self.max_y(), other.max_y());
|
||||
if x2 <= x1 or y2 <= y1
|
||||
then .zero()
|
||||
else .make(x1, y1, x2 - x1, y2 - y1);
|
||||
else .make(x1, y1, x2 - x1, y2 - y1)
|
||||
}
|
||||
|
||||
inset :: (self: Frame, insets: EdgeInsets) -> Frame {
|
||||
@@ -71,7 +71,7 @@ Frame :: struct {
|
||||
self.origin.y + insets.top,
|
||||
self.size.width - insets.left - insets.right,
|
||||
self.size.height - insets.top - insets.bottom
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
expand :: (self: Frame, amount: f32) -> Frame {
|
||||
@@ -80,45 +80,45 @@ Frame :: struct {
|
||||
self.origin.y - amount,
|
||||
self.size.width + amount * 2.0,
|
||||
self.size.height + amount * 2.0
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EdgeInsets :: struct {
|
||||
top, left, bottom, right: f32;
|
||||
|
||||
zero :: () -> EdgeInsets { EdgeInsets.{ top = 0.0, left = 0.0, bottom = 0.0, right = 0.0 }; }
|
||||
zero :: () -> EdgeInsets { EdgeInsets.{ top = 0.0, left = 0.0, bottom = 0.0, right = 0.0 } }
|
||||
|
||||
all :: (v: f32) -> EdgeInsets {
|
||||
EdgeInsets.{ top = v, left = v, bottom = v, right = v };
|
||||
EdgeInsets.{ top = v, left = v, bottom = v, right = v }
|
||||
}
|
||||
|
||||
symmetric :: (h: f32, v: f32) -> EdgeInsets {
|
||||
EdgeInsets.{ top = v, left = h, bottom = v, right = h };
|
||||
EdgeInsets.{ top = v, left = h, bottom = v, right = h }
|
||||
}
|
||||
|
||||
horizontal :: (self: EdgeInsets) -> f32 { self.left + self.right; }
|
||||
vertical :: (self: EdgeInsets) -> f32 { self.top + self.bottom; }
|
||||
horizontal :: (self: EdgeInsets) -> f32 { self.left + self.right }
|
||||
vertical :: (self: EdgeInsets) -> f32 { self.top + self.bottom }
|
||||
}
|
||||
|
||||
Color :: struct {
|
||||
r, g, b, a: u8;
|
||||
|
||||
rgba :: (r: u8, g: u8, b: u8, a: u8) -> Color {
|
||||
Color.{ r = r, g = g, b = b, a = a };
|
||||
Color.{ r = r, g = g, b = b, a = a }
|
||||
}
|
||||
|
||||
rgb :: (r: u8, g: u8, b: u8) -> Color {
|
||||
Color.{ r = r, g = g, b = b, a = 255 };
|
||||
Color.{ r = r, g = g, b = b, a = 255 }
|
||||
}
|
||||
|
||||
rf :: (self: Color) -> f32 { xx self.r / 255.0; }
|
||||
gf :: (self: Color) -> f32 { xx self.g / 255.0; }
|
||||
bf :: (self: Color) -> f32 { xx self.b / 255.0; }
|
||||
af :: (self: Color) -> f32 { xx self.a / 255.0; }
|
||||
rf :: (self: Color) -> f32 { xx self.r / 255.0 }
|
||||
gf :: (self: Color) -> f32 { xx self.g / 255.0 }
|
||||
bf :: (self: Color) -> f32 { xx self.b / 255.0 }
|
||||
af :: (self: Color) -> f32 { xx self.a / 255.0 }
|
||||
|
||||
with_alpha :: (self: Color, a: u8) -> Color {
|
||||
Color.{ r = self.r, g = self.g, b = self.b, a = a };
|
||||
Color.{ r = self.r, g = self.g, b = self.b, a = a }
|
||||
}
|
||||
|
||||
lerp :: (self: Color, b: Color, t: f32) -> Color {
|
||||
@@ -127,7 +127,7 @@ Color :: struct {
|
||||
g = xx (self.g + (b.g - self.g) * t),
|
||||
b = xx (self.b + (b.b - self.b) * t),
|
||||
a = xx (self.a + (b.a - self.a) * t),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,11 +150,11 @@ ProposedSize :: struct {
|
||||
height: ?f32;
|
||||
|
||||
fixed :: (w: f32, h: f32) -> ProposedSize {
|
||||
ProposedSize.{ width = w, height = h };
|
||||
ProposedSize.{ width = w, height = h }
|
||||
}
|
||||
|
||||
flexible :: () -> ProposedSize {
|
||||
ProposedSize.{ width = null, height = null };
|
||||
ProposedSize.{ width = null, height = null }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ ALIGN_BOTTOM_TRAILING :: Alignment.{ h = .trailing, v = .bottom };
|
||||
align_h :: (alignment: HAlignment, child_width: f32, container_width: f32) -> f32 {
|
||||
if alignment == {
|
||||
case .leading: 0.0;
|
||||
case .center: { (container_width - child_width) * 0.5; }
|
||||
case .center: { (container_width - child_width) * 0.5 }
|
||||
case .trailing: container_width - child_width;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ align_h :: (alignment: HAlignment, child_width: f32, container_width: f32) -> f3
|
||||
align_v :: (alignment: VAlignment, child_height: f32, container_height: f32) -> f32 {
|
||||
if alignment == {
|
||||
case .top: 0.0;
|
||||
case .center: { (container_height - child_height) * 0.5; }
|
||||
case .center: { (container_height - child_height) * 0.5 }
|
||||
case .bottom: container_height - child_height;
|
||||
}
|
||||
}
|
||||
@@ -209,12 +209,12 @@ align_v :: (alignment: VAlignment, child_height: f32, container_height: f32) ->
|
||||
|
||||
impl Lerpable for Point {
|
||||
lerp :: (self: Point, b: Point, t: f32) -> Point {
|
||||
Point.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t };
|
||||
Point.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t }
|
||||
}
|
||||
}
|
||||
|
||||
impl Lerpable for Size {
|
||||
lerp :: (self: Size, b: Size, t: f32) -> Size {
|
||||
Size.{ width = self.width + (b.width - self.width) * t, height = self.height + (b.height - self.height) * t };
|
||||
Size.{ width = self.width + (b.width - self.width) * t, height = self.height + (b.height - self.height) * t }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user