198 lines
7.7 KiB
Zig
198 lines
7.7 KiB
Zig
const std = @import("std");
|
||
const builtin = @import("builtin");
|
||
const dvui = @import("dvui");
|
||
const dvui_ext = @import("./ui/dvui_ext.zig");
|
||
const SDLBackend = @import("sdl-backend");
|
||
const Document = @import("Document.zig");
|
||
const WindowContext = @import("WindowContext.zig");
|
||
const sdl_c = SDLBackend.c;
|
||
const Allocator = std.mem.Allocator;
|
||
const Color = dvui.Color;
|
||
|
||
pub fn main() !void {
|
||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||
const allocator = gpa.allocator();
|
||
|
||
var backend = try SDLBackend.initWindow(.{
|
||
.allocator = allocator,
|
||
.size = .{ .w = 800.0, .h = 600.0 },
|
||
.title = "My DVUI App",
|
||
.vsync = true,
|
||
});
|
||
defer backend.deinit();
|
||
|
||
var win = try dvui.Window.init(@src(), allocator, backend.backend(), .{
|
||
.theme = switch (backend.preferredColorScheme() orelse .light) {
|
||
.light => dvui.Theme.builtin.adwaita_light,
|
||
.dark => dvui.Theme.builtin.adwaita_dark,
|
||
},
|
||
});
|
||
defer win.deinit();
|
||
|
||
var ctx = WindowContext.init(allocator);
|
||
defer ctx.deinit();
|
||
|
||
var interrupted = false;
|
||
|
||
main_loop: while (true) {
|
||
// beginWait coordinates with waitTime below to run frames only when needed
|
||
const nstime = win.beginWait(interrupted);
|
||
|
||
// marks the beginning of a frame for dvui, can call dvui functions after this
|
||
try win.begin(nstime);
|
||
|
||
// send all SDL events to dvui for processing
|
||
try backend.addAllEvents(&win);
|
||
|
||
// if dvui widgets might not cover the whole window, then need to clear
|
||
// the previous frame's render
|
||
_ = SDLBackend.c.SDL_SetRenderDrawColor(backend.renderer, 0, 0, 0, 255);
|
||
_ = SDLBackend.c.SDL_RenderClear(backend.renderer);
|
||
|
||
const keep_running = gui_frame(&ctx);
|
||
if (!keep_running) break :main_loop;
|
||
|
||
// marks end of dvui frame, don't call dvui functions after this
|
||
// - sends all dvui stuff to backend for rendering, must be called before renderPresent()
|
||
const end_micros = try win.end(.{});
|
||
|
||
// cursor management
|
||
try backend.setCursor(win.cursorRequested());
|
||
try backend.textInputRect(win.textInputRequested());
|
||
|
||
// render frame to OS
|
||
try backend.renderPresent();
|
||
|
||
// waitTime and beginWait combine to achieve variable framerates
|
||
const wait_event_micros = win.waitTime(end_micros);
|
||
interrupted = try backend.waitEventTimeout(wait_event_micros);
|
||
}
|
||
}
|
||
|
||
fn gui_frame(ctx: *WindowContext) bool {
|
||
const canvas = &ctx.canvas;
|
||
const ctrl: bool = dvui.currentWindow().modifiers.control();
|
||
|
||
for (dvui.events()) |*e| {
|
||
if (e.evt == .window and e.evt.window.action == .close) return false;
|
||
if (e.evt == .app and e.evt.app.action == .quit) return false;
|
||
}
|
||
|
||
const root = dvui.box(
|
||
@src(),
|
||
.{ .dir = .horizontal },
|
||
.{ .expand = .both, .background = true, .style = .window },
|
||
);
|
||
defer root.deinit();
|
||
|
||
// Левая панель с фиксированной шириной
|
||
var left_panel = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .vertical, .min_size_content = .{ .w = 200 }, .background = true });
|
||
{
|
||
dvui.label(@src(), "Tools", .{}, .{});
|
||
if (dvui.button(@src(), "Fill Random Color", .{}, .{})) {
|
||
canvas.fillRandomGradient() catch |err| {
|
||
std.debug.print("Error filling canvas: {}\n", .{err});
|
||
};
|
||
canvas.pos = .{ .x = 800, .y = 400 };
|
||
}
|
||
}
|
||
left_panel.deinit();
|
||
|
||
// Правая панель - занимает оставшееся пространство
|
||
const back = dvui.box(
|
||
@src(),
|
||
.{ .dir = .horizontal },
|
||
.{ .expand = .both, .padding = dvui.Rect.all(12), .background = true },
|
||
);
|
||
{
|
||
const fill_color = Color.white.opacity(0.5);
|
||
var right_panel = dvui.box(
|
||
@src(),
|
||
.{ .dir = .vertical },
|
||
.{
|
||
.expand = .both,
|
||
.background = true,
|
||
.padding = dvui.Rect.all(5),
|
||
.corner_radius = dvui.Rect.all(24),
|
||
.color_fill = fill_color,
|
||
},
|
||
);
|
||
{
|
||
var textured = dvui_ext.texturedBox(right_panel.data().contentRectScale(), dvui.Rect.all(20));
|
||
{
|
||
var overlay = dvui.overlay(
|
||
@src(),
|
||
.{ .expand = .both },
|
||
);
|
||
{
|
||
var scroll = dvui.scrollArea(
|
||
@src(),
|
||
.{
|
||
.scroll_info = &canvas.scroll,
|
||
.vertical_bar = .auto,
|
||
.horizontal_bar = .auto,
|
||
},
|
||
.{
|
||
.expand = .both,
|
||
.background = false,
|
||
},
|
||
);
|
||
{
|
||
// Отобразить canvas внутри scroll area.
|
||
// ScrollArea сам двигает дочерние виджеты, поэтому margin не нужен.
|
||
if (canvas.texture) |texture| {
|
||
const img_size = canvas.getScaledSize();
|
||
_ = dvui.image(@src(), .{
|
||
.source = .{ .texture = texture },
|
||
}, .{
|
||
.margin = .{ .x = canvas.pos.x, .y = canvas.pos.y },
|
||
.min_size_content = .{
|
||
.w = img_size.w,
|
||
.h = img_size.h,
|
||
},
|
||
});
|
||
|
||
// Получить viewport и scroll offset
|
||
const viewport_rect = scroll.data().contentRect();
|
||
const scroll_current = dvui.Point{ .x = canvas.scroll.viewport.x, .y = canvas.scroll.viewport.y };
|
||
const visible_rect = canvas.getVisibleImageRect(viewport_rect, scroll_current);
|
||
std.debug.print("Visible image rect: {any}\n", .{visible_rect});
|
||
}
|
||
|
||
// Заблокировать события скролла, если нажат ctrl
|
||
if (ctrl) {
|
||
for (dvui.events()) |*e| {
|
||
switch (e.evt) {
|
||
.mouse => |mouse| {
|
||
const action = mouse.action;
|
||
if (dvui.eventMatchSimple(e, scroll.data()) and (action == .wheel_x or action == .wheel_y)) {
|
||
switch (action) {
|
||
.wheel_y => |y| {
|
||
canvas.addZoom(y / 1000);
|
||
canvas.redrawGradient() catch {};
|
||
},
|
||
else => {},
|
||
}
|
||
e.handled = true;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
}
|
||
scroll.deinit();
|
||
|
||
dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 });
|
||
}
|
||
overlay.deinit();
|
||
}
|
||
textured.deinit();
|
||
}
|
||
right_panel.deinit();
|
||
}
|
||
back.deinit();
|
||
|
||
return true;
|
||
}
|