apprt/gtk: add View to help with auto-layout views

This commit is contained in:
Mitchell Hashimoto
2023-09-17 21:59:25 -07:00
parent bd3b5d5332
commit fe15e7d5fd
2 changed files with 128 additions and 69 deletions

View File

@ -7,14 +7,13 @@ const configpkg = @import("../../config.zig");
const Config = configpkg.Config; const Config = configpkg.Config;
const App = @import("App.zig"); const App = @import("App.zig");
const View = @import("View.zig");
const c = @import("c.zig"); const c = @import("c.zig");
const log = std.log.scoped(.gtk); const log = std.log.scoped(.gtk);
app: *App, app: *App,
layout: *c.GtkConstraintLayout,
pub fn create(app: *App) !void { pub fn create(app: *App) !void {
if (app.config_errors_window != null) return error.InvalidOperation; if (app.config_errors_window != null) return error.InvalidOperation;
@ -28,8 +27,6 @@ pub fn create(app: *App) !void {
/// Not public because this should be called by the GTK lifecycle. /// Not public because this should be called by the GTK lifecycle.
fn destroy(self: *ConfigErrors) void { fn destroy(self: *ConfigErrors) void {
c.g_object_unref(self.layout);
const alloc = self.app.core_app.alloc; const alloc = self.app.core_app.alloc;
self.app.config_errors_window = null; self.app.config_errors_window = null;
alloc.destroy(self); alloc.destroy(self);
@ -43,75 +40,33 @@ fn init(self: *ConfigErrors, app: *App) !void {
c.gtk_window_set_title(gtk_window, "Configuration Errors"); c.gtk_window_set_title(gtk_window, "Configuration Errors");
c.gtk_window_set_default_size(gtk_window, 600, 300); c.gtk_window_set_default_size(gtk_window, 600, 300);
// Box to store our widgets // All our widgets
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 12);
c.gtk_widget_set_vexpand(box, 1);
c.gtk_widget_set_hexpand(box, 1);
c.gtk_window_set_child(@ptrCast(window), box);
// We use a constraint-based layout so the window is resizeable
const layout = c.gtk_constraint_layout_new();
errdefer c.g_object_unref(layout);
c.gtk_widget_set_layout_manager(@ptrCast(box), layout);
// Create all of our widgets
const label = c.gtk_label_new( const label = c.gtk_label_new(
"One or more configuration errors were found while loading " ++ "One or more configuration errors were found while loading " ++
"the configuration. Please review the errors below and reload " ++ "the configuration. Please review the errors below and reload " ++
"your configuration or ignore the erroneous lines.", "your configuration or ignore the erroneous lines.",
); );
c.gtk_label_set_wrap(@ptrCast(label), 1);
const buf = try contentsBuffer(&app.config); const buf = try contentsBuffer(&app.config);
defer c.g_object_unref(buf); defer c.g_object_unref(buf);
const text = c.gtk_text_view_new_with_buffer(buf); const text = c.gtk_text_view_new_with_buffer(buf);
errdefer c.g_object_unref(text); errdefer c.g_object_unref(text);
c.gtk_text_view_set_editable(@ptrCast(text), 0);
c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0);
const ignore_button = c.gtk_button_new_with_label("Ignore"); var buttons = try ButtonsView.init(self);
errdefer c.g_object_unref(ignore_button); errdefer buttons.deinit();
const reload_button = c.gtk_button_new_with_label("Reload Configuration"); // Create our view
errdefer c.g_object_unref(reload_button); const view = try View.init(&.{
.{ .name = "label", .widget = label },
// This hooks up all our widgets to the window so they can be laid out .{ .name = "text", .widget = text },
// using the constraint-based layout. .{ .name = "buttons", .widget = buttons.view.root },
c.gtk_widget_set_parent(label, box); }, &rootvfl);
c.gtk_widget_set_name(label, "label"); errdefer view.deinit();
c.gtk_widget_set_parent(text, box);
c.gtk_widget_set_name(text, "text");
c.gtk_widget_set_parent(ignore_button, box);
c.gtk_widget_set_name(ignore_button, "ignorebutton");
c.gtk_widget_set_parent(reload_button, box);
c.gtk_widget_set_name(reload_button, "reloadbutton");
var gerr: ?*c.GError = null;
const list = c.gtk_constraint_layout_add_constraints_from_description(
@ptrCast(layout),
&vfl,
vfl.len,
8,
8,
&gerr,
"label",
label,
"text",
text,
"ignorebutton",
ignore_button,
"reloadbutton",
reload_button,
@as(?*anyopaque, null),
);
if (gerr) |err| {
defer c.g_error_free(err);
log.warn("error building window message={s}", .{err.message});
return error.OperationFailed;
}
c.g_list_free(list);
// We can do additional settings once the layout is setup // We can do additional settings once the layout is setup
c.gtk_label_set_wrap(@ptrCast(label), 1);
c.gtk_text_view_set_editable(@ptrCast(text), 0);
c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0);
c.gtk_text_view_set_top_margin(@ptrCast(text), 8); c.gtk_text_view_set_top_margin(@ptrCast(text), 8);
c.gtk_text_view_set_bottom_margin(@ptrCast(text), 8); c.gtk_text_view_set_bottom_margin(@ptrCast(text), 8);
c.gtk_text_view_set_left_margin(@ptrCast(text), 8); c.gtk_text_view_set_left_margin(@ptrCast(text), 8);
@ -121,13 +76,11 @@ fn init(self: *ConfigErrors, app: *App) !void {
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(&gtkDestroy), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(&gtkDestroy), self, null, c.G_CONNECT_DEFAULT);
// Show the window // Show the window
c.gtk_window_set_child(@ptrCast(window), view.root);
c.gtk_widget_show(window); c.gtk_widget_show(window);
// Set some state // Set some state
self.* = .{ self.* = .{ .app = app };
.app = app,
.layout = @ptrCast(layout),
};
} }
fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
@ -152,12 +105,41 @@ fn contentsBuffer(config: *const Config) !*c.GtkTextBuffer {
return buf; return buf;
} }
const vfl = [_][*:0]const u8{ const ButtonsView = struct {
view: View,
pub fn init(root: *ConfigErrors) !ButtonsView {
// Todo for events
_ = root;
const ignore_button = c.gtk_button_new_with_label("Ignore");
errdefer c.g_object_unref(ignore_button);
const reload_button = c.gtk_button_new_with_label("Reload Configuration");
errdefer c.g_object_unref(reload_button);
// Create our view
const view = try View.init(&.{
.{ .name = "ignore", .widget = ignore_button },
.{ .name = "reload", .widget = reload_button },
}, &vfl);
errdefer view.deinit();
return .{ .view = view };
}
pub fn deinit(self: *ButtonsView) void {
self.view.deinit();
}
const vfl = [_][*:0]const u8{
"H:[ignore]-8-[reload]-8-|",
};
};
const rootvfl = [_][*:0]const u8{
"H:|-8-[label]-8-|", "H:|-8-[label]-8-|",
"H:|[text]|", "H:|[text]|",
"H:[ignorebutton]-8-[reloadbutton]-8-|", "H:|[buttons]|",
"V:|[label(<=100)][text(>=100)]-[ignorebutton]-|", "V:|[label(<=100)][text(>=100)]-[buttons]-|",
"V:|[label(<=100)][text(>=100)]-[reloadbutton]-|",
"V:[label][text]-[ignorebutton]",
"V:[label][text]-[reloadbutton]",
}; };

77
src/apprt/gtk/View.zig Normal file
View File

@ -0,0 +1,77 @@
/// View helps with creating a view with a constraint layout by
/// managing all the boilerplate. The caller is responsible for
/// providing the widgets, their names, and the VFL code and gets
/// a root box as a result ready to be used.
const View = @This();
const std = @import("std");
const c = @import("c.zig");
const log = std.log.scoped(.gtk);
/// The box that contains all of the widgets.
root: *c.GtkWidget,
/// A single widget used in the view.
pub const Widget = struct {
/// The name of the widget used for the layout code. This is also
/// the name set for the widget for CSS styling.
name: [:0]const u8,
/// The widget itself.
widget: *c.GtkWidget,
};
/// Initialize a new constraint layout view with the given widgets
/// and VFL.
pub fn init(widgets: []const Widget, vfl: []const [*:0]const u8) !View {
// Box to store all our widgets
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
errdefer c.g_object_unref(box);
c.gtk_widget_set_vexpand(box, 1);
c.gtk_widget_set_hexpand(box, 1);
// Setup our constraint layout and attach it to the box
const layout = c.gtk_constraint_layout_new();
errdefer c.g_object_unref(layout);
c.gtk_widget_set_layout_manager(@ptrCast(box), layout);
// Setup our views table
const views = c.g_hash_table_new(c.g_str_hash, c.g_str_equal);
defer c.g_hash_table_unref(views);
// Add our widgets
for (widgets) |widget| {
c.gtk_widget_set_parent(widget.widget, box);
c.gtk_widget_set_name(widget.widget, widget.name);
_ = c.g_hash_table_insert(
views,
@constCast(@ptrCast(widget.name.ptr)),
widget.widget,
);
}
// Add all of our constraints for layout
var err_: ?*c.GError = null;
const list = c.gtk_constraint_layout_add_constraints_from_descriptionv(
@ptrCast(layout),
vfl.ptr,
vfl.len,
8,
8,
views,
&err_,
);
if (err_) |err| {
defer c.g_error_free(err);
log.warn("error building view message={s}", .{err.message});
return error.OperationFailed;
}
c.g_list_free(list);
return .{ .root = box };
}
pub fn deinit(self: *View) void {
_ = self;
}