...
This commit is contained in:
253
main.sx
253
main.sx
@@ -1,13 +1,65 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
#import "modules/sdl3.sx";
|
||||
#import "modules/opengl.sx";
|
||||
#import "modules/math";
|
||||
#import "modules/stb.sx";
|
||||
#import "modules/stb_truetype.sx";
|
||||
#import "ui";
|
||||
|
||||
configure_build :: () {
|
||||
opts := build_options();
|
||||
inline if OS == {
|
||||
|
||||
case .wasm: {
|
||||
inline if POINTER_SIZE == 4 {
|
||||
opts.set_output_path("sx-out/wasm32/game.html");
|
||||
} else {
|
||||
opts.set_output_path("sx-out/wasm64/game.html");
|
||||
}
|
||||
|
||||
opts.add_link_flag("-sUSE_SDL=3");
|
||||
opts.add_link_flag("-sMAX_WEBGL_VERSION=2");
|
||||
opts.add_link_flag("-sFULL_ES3=1");
|
||||
opts.add_link_flag("--preload-file assets");
|
||||
opts.add_link_flag("-sALLOW_MEMORY_GROWTH=1");
|
||||
}
|
||||
case .macos: {
|
||||
opts.set_output_path("sx-out/macos/game");
|
||||
}
|
||||
}
|
||||
}
|
||||
#run configure_build();
|
||||
|
||||
libc :: #library "c";
|
||||
emscripten_set_main_loop :: (func: *void, fps: s32, sim_infinite: s32) #foreign libc;
|
||||
|
||||
WIDTH :f32: 800;
|
||||
HEIGHT :f32: 600;
|
||||
|
||||
// --- Frame state (globals for emscripten callback) ---
|
||||
g_window : *void = ---;
|
||||
g_pipeline : *UIPipeline = ---;
|
||||
g_running : bool = true;
|
||||
|
||||
load_texture :: (path: [:0]u8) -> u32 {
|
||||
w : s32 = 0;
|
||||
h : s32 = 0;
|
||||
ch : s32 = 0;
|
||||
pixels := stbi_load(path, @w, @h, @ch, 4);
|
||||
if xx pixels == 0 { return 0; }
|
||||
|
||||
tex : u32 = 0;
|
||||
glGenTextures(1, @tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
|
||||
|
||||
stbi_image_free(pixels);
|
||||
tex;
|
||||
}
|
||||
|
||||
save_snapshot :: (path: [:0]u8, w: s32, h: s32) {
|
||||
stride : s64 = w * 4;
|
||||
buf_size : s64 = stride * h;
|
||||
@@ -32,13 +84,114 @@ save_snapshot :: (path: [:0]u8, w: s32, h: s32) {
|
||||
out("\n");
|
||||
}
|
||||
|
||||
run_ui_tests :: (pipeline: *UIPipeline) {
|
||||
// Do a layout pass first so frames are computed
|
||||
glClearColor(0.12, 0.12, 0.15, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
pipeline.tick();
|
||||
|
||||
// Button "Click Me" is roughly at center x=400, y=165 based on golden
|
||||
btn_x : f32 = 400.0;
|
||||
btn_y : f32 = 165.0;
|
||||
btn_pos := Point.{ x = btn_x, y = btn_y };
|
||||
// Empty area below the rects
|
||||
empty_x : f32 = 400.0;
|
||||
empty_y : f32 = 500.0;
|
||||
|
||||
// --- Test 1: Click button (no drag) ---
|
||||
out("=== Test 1: button click ===\n");
|
||||
out("expect: Button tapped!\n");
|
||||
e :Event = .mouse_down(MouseButtonData.{ position = btn_pos, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
e = .mouse_up(MouseButtonData.{ position = btn_pos, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
out("=== end test 1 ===\n\n");
|
||||
|
||||
// --- Test 2: Drag from button area (should scroll, NOT tap) ---
|
||||
out("=== Test 2: drag from button ===\n");
|
||||
out("expect: NO Button tapped, scroll should move\n");
|
||||
e = .mouse_down(MouseButtonData.{ position = btn_pos, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
// Move down 50px in steps (exceeds 4px threshold)
|
||||
i : s32 = 1;
|
||||
while i <= 10 {
|
||||
e = .mouse_moved(MouseMotionData.{
|
||||
position = Point.{ x = btn_x, y = btn_y + xx i * 5.0 },
|
||||
delta = Point.{ x = 0.0, y = 5.0 }
|
||||
});
|
||||
pipeline.dispatch_event(@e);
|
||||
i += 1;
|
||||
}
|
||||
e = .mouse_up(MouseButtonData.{ position = Point.{ x = btn_x, y = btn_y + 50.0 }, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
out("=== end test 2 ===\n\n");
|
||||
|
||||
// --- Test 3: Drag from empty area (should scroll) ---
|
||||
out("=== Test 3: drag from empty area ===\n");
|
||||
out("expect: scroll should move\n");
|
||||
e = .mouse_down(MouseButtonData.{ position = Point.{ x = empty_x, y = empty_y }, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
i = 1;
|
||||
while i <= 10 {
|
||||
e = .mouse_moved(MouseMotionData.{
|
||||
position = Point.{ x = empty_x, y = empty_y - xx i * 5.0 },
|
||||
delta = Point.{ x = 0.0, y = 0.0 - 5.0 }
|
||||
});
|
||||
pipeline.dispatch_event(@e);
|
||||
i += 1;
|
||||
}
|
||||
e = .mouse_up(MouseButtonData.{ position = Point.{ x = empty_x, y = empty_y - 50.0 }, button = .left });
|
||||
pipeline.dispatch_event(@e);
|
||||
out("=== end test 3 ===\n\n");
|
||||
|
||||
// Render after tests and save snapshot to see scrolled state
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
pipeline.tick();
|
||||
save_snapshot("goldens/test_after_drag.png", xx WIDTH, xx HEIGHT);
|
||||
}
|
||||
|
||||
// One frame of the main loop — called repeatedly by emscripten or desktop while-loop
|
||||
frame :: () {
|
||||
sdl_event : SDL_Event = .none;
|
||||
while SDL_PollEvent(sdl_event) {
|
||||
print("SDL event: {}\n", sdl_event.tag);
|
||||
|
||||
if sdl_event == {
|
||||
case .quit: { g_running = false; }
|
||||
case .key_up: (e) {
|
||||
if e.key == { case .escape: { g_running = false; } }
|
||||
}
|
||||
}
|
||||
|
||||
ui_event := translate_sdl_event(@sdl_event);
|
||||
if ui_event != .none {
|
||||
print(" ui event dispatched\n");
|
||||
g_pipeline.*.dispatch_event(@ui_event);
|
||||
} else {
|
||||
print(" -> .none\n");
|
||||
}
|
||||
}
|
||||
|
||||
glClearColor(0.12, 0.12, 0.15, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
g_pipeline.*.tick();
|
||||
|
||||
SDL_GL_SwapWindow(g_window);
|
||||
}
|
||||
|
||||
main :: () -> void {
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
inline if OS == .wasm {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
}
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
|
||||
@@ -47,73 +200,55 @@ main :: () -> void {
|
||||
SDL_GL_MakeCurrent(window, gl_ctx);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
load_gl(SDL_GL_GetProcAddress);
|
||||
load_gl_ui(SDL_GL_GetProcAddress);
|
||||
load_gl(xx SDL_GL_GetProcAddress);
|
||||
|
||||
// --- Build UI ---
|
||||
pipeline : UIPipeline = ---;
|
||||
pipeline.init(WIDTH, HEIGHT);
|
||||
pipeline.init_font("assets/fonts/default.ttf", 32.0);
|
||||
|
||||
// Create a simple layout: VStack with colored rects and a button
|
||||
root := VStack.{ spacing = 10.0, alignment = .center };
|
||||
scroll_content := VStack.{ spacing = 10.0, alignment = .center } {
|
||||
self.add(
|
||||
Label.{ text = "Hello, SX!", font_size = 24.0, color = COLOR_WHITE }
|
||||
|> padding(EdgeInsets.all(8.0))
|
||||
);
|
||||
self.add(
|
||||
RectView.{ color = COLOR_YELLOW, preferred_height = 80.0, corner_radius = 8.0 }
|
||||
|> padding(EdgeInsets.all(8.0))
|
||||
|> on_tap(closure(() { out("Yellow tapped!\n"); }))
|
||||
);
|
||||
self.add(
|
||||
Button.{ label = "Click Me", font_size = 14.0, style = ButtonStyle.default(), on_tap = closure(() { out("Button tapped!\n"); }) }
|
||||
);
|
||||
self.add(HStack.{ spacing = 10.0, alignment = .center } {
|
||||
self.add(RectView.{ color = COLOR_RED, preferred_width = 200.0, preferred_height = 300.0, corner_radius = 4.0 });
|
||||
self.add(RectView.{ color = COLOR_GREEN, preferred_width = 200.0, preferred_height = 300.0, corner_radius = 4.0 });
|
||||
});
|
||||
self.add(
|
||||
RectView.{ color = COLOR_DARK_GRAY, preferred_height = 60.0 }
|
||||
|> padding(EdgeInsets.symmetric(16.0, 8.0))
|
||||
|> background(COLOR_BLUE, 8.0)
|
||||
);
|
||||
self.add(RectView.{ color = COLOR_ORANGE, preferred_height = 120.0, corner_radius = 12.0 });
|
||||
self.add(RectView.{ color = COLOR_GRAY, preferred_height = 200.0, corner_radius = 8.0 });
|
||||
};
|
||||
|
||||
header := RectView.{ color = COLOR_YELLOW, preferred_height = 80.0, corner_radius = 8.0 };
|
||||
root.add(xx header);
|
||||
root := ScrollView.{ child = ViewChild.{ view = scroll_content }, axes = .vertical };
|
||||
pipeline.set_root(root);
|
||||
|
||||
btn := Button.{ label = "Click Me", font_size = 14.0, style = ButtonStyle.default(), on_tap = null };
|
||||
root.add(xx btn);
|
||||
|
||||
body := HStack.{ spacing = 10.0, alignment = .center };
|
||||
|
||||
left := RectView.{ color = COLOR_RED, preferred_width = 200.0, preferred_height = 300.0, corner_radius = 4.0 };
|
||||
body.add(xx left);
|
||||
|
||||
right := RectView.{ color = COLOR_GREEN, preferred_width = 200.0, preferred_height = 300.0, corner_radius = 4.0 };
|
||||
body.add(xx right);
|
||||
|
||||
root.add(xx body);
|
||||
|
||||
footer := RectView.{ color = COLOR_DARK_GRAY, preferred_height = 60.0 };
|
||||
root.add(xx footer);
|
||||
|
||||
pipeline.set_root(xx root);
|
||||
// Store state in globals for frame callback
|
||||
g_window = xx window;
|
||||
g_pipeline = @pipeline;
|
||||
|
||||
// --- Main loop ---
|
||||
running := true;
|
||||
sdl_event : SDL_Event = .none;
|
||||
|
||||
while running {
|
||||
while SDL_PollEvent(sdl_event) {
|
||||
if sdl_event == {
|
||||
case .quit: running = false;
|
||||
case .key_up: (e) {
|
||||
if e.key == {
|
||||
case .escape: running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward to UI
|
||||
ui_event := translate_sdl_event(@sdl_event);
|
||||
if ui_event.type != .none {
|
||||
pipeline.dispatch_event(@ui_event);
|
||||
}
|
||||
inline if OS == .wasm {
|
||||
emscripten_set_main_loop(xx frame, 0, 1);
|
||||
} else {
|
||||
while g_running {
|
||||
frame();
|
||||
}
|
||||
|
||||
glClearColor(0.12, 0.12, 0.15, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
pipeline.tick();
|
||||
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
// Re-render one frame for snapshot (back buffer is stale after swap)
|
||||
glClearColor(0.12, 0.12, 0.15, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
pipeline.tick();
|
||||
save_snapshot("goldens/last_frame.png", xx WIDTH, xx HEIGHT);
|
||||
|
||||
SDL_GL_DestroyContext(gl_ctx);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
Reference in New Issue
Block a user