Skip to content

Commit 8749731

Browse files
committed
Refactor and test rendering and view structures
- Added unit tests for rendering shapes and layouts: `test_render_rectangle`, `test_render_circle`, and `test_render_layout_clip_push_pop`. - Introduced `DrawClip` push/pop validation for nested layouts in render tests. - Merged and streamlined `TextCfg` and `ContainerCfg` with corresponding `TextView` and `ContainerView` implementations. - Updated `view_text.v` to remove redundant `cfg` references and improved consistency for event interactions. - Renamed `rtf_simple` to `rtf_simple_wrap` for clarity in `view_rtf.v`. - Added `[minify]` annotations for structs to optimize memory usage (`InputState`, `TextView`, etc.). - Miscellaneous improvements to rendering logic, inline declarations, and naming for clarity (`sgl_arc_triangle_strip_with_offset`, etc.). - Adjusted and added size stats in `stats.v` for new/modified structures. - Applied consistent parameter passing across mouse and key event handlers.
1 parent 48037d4 commit 8749731

File tree

13 files changed

+283
-129
lines changed

13 files changed

+283
-129
lines changed

event.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn event_relative_to(shape &Shape, e &Event) &Event {
5454
}
5555
}
5656

57+
@[minify]
5758
pub struct Event {
5859
pub mut:
5960
touches [8]TouchPoint

examples/test_layout.v

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ fn main_view(w &gui.Window) gui.View {
168168
}
169169
),
170170
gui.column(
171+
color: gui.theme().text_style.color
172+
fill: false
171173
sizing: gui.fill_fit
172174
text: ' mode = .wrap '
173175
content: [
@@ -179,6 +181,8 @@ fn main_view(w &gui.Window) gui.View {
179181
]
180182
),
181183
gui.column(
184+
color: gui.theme().text_style.color
185+
fill: false
182186
sizing: gui.fill_fit
183187
text: ' model = .wrap_keep_spaces '
184188
content: [

render.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn renderers_draw(renderers []Renderer, window &Window) {
6363

6464
// renderer_draw draws a single renderer
6565
fn renderer_draw(renderer Renderer, window &Window) {
66-
ctx := window.ui
66+
mut ctx := window.ui
6767
match renderer {
6868
DrawRect {
6969
if renderer.style == .fill {

render_test.v

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
module gui
2+
3+
import gg
4+
5+
// Helpers
6+
fn make_window() Window {
7+
// Minimal window; we do not touch window.ui in these tests
8+
mut w := Window{}
9+
// ensure clean renderers
10+
w.renderers = []
11+
return w
12+
}
13+
14+
fn make_clip(x f32, y f32, w f32, h f32) DrawClip {
15+
return gg.Rect{
16+
x: x
17+
y: y
18+
width: w
19+
height: h
20+
}
21+
}
22+
23+
// -----------------------------
24+
// rects_overlap basic behavior
25+
// -----------------------------
26+
fn test_rects_overlap() {
27+
a := make_clip(0, 0, 10, 10)
28+
b := make_clip(5, 5, 10, 10)
29+
c := make_clip(10, 0, 5, 5) // touches edge at x=10
30+
31+
assert rects_overlap(a, b)
32+
assert !rects_overlap(a, c) // touching edge is not overlapping (strict <)
33+
}
34+
35+
// -----------------------------
36+
// dim_alpha halves the alpha
37+
// -----------------------------
38+
fn test_dim_alpha() {
39+
c := rgba(10, 20, 30, 201)
40+
d := dim_alpha(c)
41+
assert d.r == c.r
42+
assert d.g == c.g
43+
assert d.b == c.b
44+
// Integer division by 2
45+
assert d.a == u8(201 / 2)
46+
}
47+
48+
// --------------------------------------------
49+
// render_rectangle emits a single DrawRect
50+
// --------------------------------------------
51+
fn test_render_rectangle_inside_clip() {
52+
mut w := make_window()
53+
mut s := Shape{
54+
shape_type: .rectangle
55+
x: 10
56+
y: 20
57+
width: 30
58+
height: 40
59+
color: rgb(100, 150, 200)
60+
fill: true
61+
radius: 5
62+
}
63+
clip := make_clip(0, 0, 200, 200)
64+
65+
render_rectangle(mut s, clip, mut w)
66+
67+
assert w.renderers.len == 1
68+
r := w.renderers[0]
69+
match r {
70+
DrawRect {
71+
assert r.x == s.x
72+
assert r.y == s.y
73+
assert r.w == s.width
74+
assert r.h == s.height
75+
assert r.style == .fill
76+
assert r.is_rounded
77+
assert r.radius == s.radius
78+
assert r.color == s.color.to_gx_color()
79+
}
80+
else {
81+
assert false, 'expected DrawRect'
82+
}
83+
}
84+
}
85+
86+
fn test_render_rectangle_outside_clip_disables_shape() {
87+
mut w := make_window()
88+
mut s := Shape{
89+
shape_type: .rectangle
90+
x: 100
91+
y: 100
92+
width: 20
93+
height: 20
94+
color: rgb(10, 10, 10)
95+
fill: false
96+
}
97+
clip := make_clip(0, 0, 50, 50)
98+
99+
render_rectangle(mut s, clip, mut w)
100+
101+
assert w.renderers.len == 0
102+
assert s.disabled
103+
}
104+
105+
// ----------------------------------------
106+
// render_circle emits a single DrawCircle
107+
// ----------------------------------------
108+
fn test_render_circle_inside_clip() {
109+
mut w := make_window()
110+
mut s := Shape{
111+
shape_type: .circle
112+
x: 0
113+
y: 0
114+
width: 40
115+
height: 20
116+
color: rgb(1, 2, 3)
117+
fill: false
118+
}
119+
clip := make_clip(-10, -10, 100, 100)
120+
121+
render_circle(mut s, clip, mut w)
122+
123+
assert w.renderers.len == 1
124+
c := w.renderers[0]
125+
match c {
126+
DrawCircle {
127+
// Center should be at (x + w/2, y + h/2)
128+
assert f32_are_close(c.x, s.x + s.width / 2)
129+
assert f32_are_close(c.y, s.y + s.height / 2)
130+
// Radius is half of the shortest side
131+
assert f32_are_close(c.radius, f32_min(s.width, s.height) / 2)
132+
assert c.fill == s.fill
133+
assert c.color == s.color.to_gx_color()
134+
}
135+
else {
136+
assert false, 'expected DrawCircle'
137+
}
138+
}
139+
}
140+
141+
// --------------------------------------------------------
142+
// render_layout: clip push before children, pop after
143+
// --------------------------------------------------------
144+
fn test_render_layout_clip_push_pop() {
145+
mut w := make_window()
146+
mut root := Layout{
147+
shape: &Shape{
148+
// Keep it invisible as a container to avoid text/container drawing
149+
color: color_transparent
150+
clip: true
151+
padding: Padding{
152+
left: 2
153+
right: 3
154+
top: 4
155+
bottom: 5
156+
}
157+
shape_clip: make_clip(10, 20, 100, 50)
158+
}
159+
children: []
160+
}
161+
162+
initial_clip := make_clip(0, 0, 400, 400)
163+
bg := rgb(0, 0, 0)
164+
165+
render_layout(mut root, bg, initial_clip, mut w)
166+
167+
// Expect two clips: computed shape_clip (with padding applied), then pop back to initial
168+
assert w.renderers.len == 2
169+
sc_push := w.renderers[0]
170+
sc_pop := w.renderers[1]
171+
172+
match sc_push {
173+
DrawClip {
174+
assert f32_are_close(sc_push.x, 10 + 2)
175+
assert f32_are_close(sc_push.y, 20 + 4)
176+
assert f32_are_close(sc_push.width, 100 - (2 + 3))
177+
assert f32_are_close(sc_push.height, 50 - (4 + 5))
178+
}
179+
else {
180+
assert false, 'expected first renderer to be DrawClip (push)'
181+
}
182+
}
183+
match sc_pop {
184+
DrawClip {
185+
assert f32_are_close(sc_pop.x, initial_clip.x)
186+
assert f32_are_close(sc_pop.y, initial_clip.y)
187+
assert f32_are_close(sc_pop.width, initial_clip.width)
188+
assert f32_are_close(sc_pop.height, initial_clip.height)
189+
}
190+
else {
191+
assert false, 'expected second renderer to be DrawClip (pop)'
192+
}
193+
}
194+
}

stats.v

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ fn struct_sizes() string {
106106
tx << ''
107107
tx << 'Various Struct Sizes'
108108
tx << stat_sub_div
109+
tx << 'Layout ${sizeof(Layout):8}'
109110
tx << 'Shape ${sizeof(Shape):8}'
110111
tx << 'ContainerView ${sizeof(ContainerView):8}'
111112
tx << 'ContainerCfg ${sizeof(ContainerCfg):8}'
113+
tx << 'TextView ${sizeof(TextView):8}'
112114
tx << 'TextCfg ${sizeof(TextCfg):8}'
113115
tx << 'TextStyle ${sizeof(TextStyle):8}'
114-
tx << 'TextView ${sizeof(TextView):8}'
115116
tx << '[]View ${sizeof([]View):8}'
116117
return tx.join('\n')
117118
}
@@ -125,8 +126,8 @@ fn (vs ViewState) view_state_stats() string {
125126
tx << 'offset_x_state length ${cm(usize(vs.offset_x_state.len)):8}'
126127
tx << 'offset_y_state length ${cm(usize(vs.offset_y_state.len)):8}'
127128
tx << 'text_widths length ${cm(usize(vs.text_widths.len)):8}'
128-
tx << 'menu_state length ${cm(usize(vs.menu_state.len)):8}'
129-
tx << 'image_map length ${cm(usize(vs.image_map.len)):8}'
129+
tx << 'menu_state length ${cm(usize(vs.menu_state.len)):8}'
130+
tx << 'image_map length ${cm(usize(vs.image_map.len)):8}'
130131
tx << 'select_state length ${cm(usize(vs.select_state.len)):8}'
131132
tx << 'tree_state length ${cm(usize(vs.tree_state.len)):8}'
132133
tx << 'date_picker_state length ${cm(usize(vs.date_picker_state.len)):8}'

view_container.v

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,51 +25,10 @@ import arrays
2525

2626
@[minify]
2727
struct ContainerView implements View {
28-
pub:
29-
id string
30-
name string // used internally, read-only
31-
text string
32-
color Color = gui_theme.container_style.color
33-
padding Padding = gui_theme.container_style.padding
34-
sizing Sizing
35-
x f32
36-
y f32
37-
width f32
38-
min_width f32
39-
max_width f32
40-
height f32
41-
min_height f32
42-
max_height f32
43-
radius f32 = gui_theme.container_style.radius
44-
spacing f32 = gui_theme.container_style.spacing
45-
float_offset_x f32
46-
float_offset_y f32
47-
id_focus u32
48-
id_scroll u32
49-
h_align HorizontalAlign
50-
v_align VerticalAlign
51-
scroll_mode ScrollMode
52-
float_anchor FloatAttach
53-
float_tie_off FloatAttach
54-
fill bool = gui_theme.container_style.fill
55-
clip bool
56-
focus_skip bool
57-
disabled bool
58-
invisible bool
59-
float bool
60-
over_draw bool
28+
ContainerCfg
6129
mut:
62-
content []View
63-
tooltip &TooltipCfg = unsafe { nil }
64-
on_char fn (&Layout, mut Event, mut Window) = unsafe { nil }
65-
on_click fn (&Layout, mut Event, mut Window) = unsafe { nil }
66-
on_keydown fn (&Layout, mut Event, mut Window) = unsafe { nil }
67-
on_mouse_move fn (&Layout, mut Event, mut Window) = unsafe { nil }
68-
on_mouse_up fn (&Layout, mut Event, mut Window) = unsafe { nil }
69-
amend_layout fn (mut Layout, mut Window) = unsafe { nil }
70-
on_hover fn (mut Layout, mut Event, mut Window) = unsafe { nil }
71-
shape_type ShapeType = .rectangle
72-
axis Axis
30+
content []View
31+
shape_type ShapeType = .rectangle
7332
}
7433

7534
fn (mut cv ContainerView) generate_layout(mut _ Window) Layout {

view_image.v

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,9 @@ import log
44

55
@[minify]
66
struct ImageView implements View {
7-
pub:
8-
id string
9-
file_name string
10-
width f32
11-
height f32
12-
min_width f32
13-
min_height f32
14-
max_width f32
15-
max_height f32
16-
invisible bool
7+
ImageCfg
178
mut:
18-
on_click fn (&Layout, mut Event, mut Window) = unsafe { nil }
19-
on_hover fn (mut Layout, mut Event, mut Window) = unsafe { nil }
20-
content []View // not used
9+
content []View // not used
2110
}
2211

2312
@[minify]

view_input.v

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import arrays
2020
// because...they're stateless. Instead, the window maintains this state in a
2121
// map where the key is the w.view_state.id_focus. This state map is cleared when a new
2222
// view is introduced.
23+
@[minify]
2324
struct InputState {
2425
pub:
2526
// positions are number of runes relative to start of input text
@@ -30,6 +31,7 @@ pub:
3031
redo datatypes.Stack[InputMemento]
3132
}
3233

34+
@[minify]
3335
struct InputMemento {
3436
pub:
3537
text string

view_rtf.v

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,9 @@ import os
99

1010
@[minify]
1111
struct RtfView implements View {
12+
RtfCfg
1213
pub:
13-
id string
14-
sizing Sizing
15-
min_width f32
16-
id_focus u32
17-
mode TextMode
18-
invisible bool
19-
clip bool
20-
focus_skip bool
21-
disabled bool
14+
sizing Sizing
2215
pub mut:
2316
spans datatypes.LinkedList[TextSpan]
2417
content []View // required, not used
@@ -29,6 +22,7 @@ pub mut:
2922
// faces and sizes are specified as [TextSpan](#TextSpan)s.
3023
// Note: TextMode.wrap and TextMode.wrap_keep_spaces are the
3124
// same for RTF.
25+
@[minify]
3226
pub struct RtfCfg {
3327
pub:
3428
id string
@@ -49,7 +43,7 @@ fn (mut rtf RtfView) generate_layout(mut window Window) Layout {
4943

5044
tspans := match true {
5145
rtf.mode in [.wrap, .wrap_keep_spaces] { rtf.spans }
52-
else { rtf_simple(rtf.spans, mut window) }
46+
else { rtf_simple_wrap(rtf.spans, mut window) }
5347
}
5448
width, height := spans_size(tspans)
5549

0 commit comments

Comments
 (0)