mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
gtk-ng: move actions helper to namespace
This commit is contained in:
@@ -1112,7 +1112,7 @@ pub const Application = extern struct {
|
||||
const as_variant_type = glib.VariantType.new("as");
|
||||
defer as_variant_type.free();
|
||||
|
||||
const actions = [_]ext.Action(Self){
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("new-window", actionNewWindow, null),
|
||||
.init("new-window-command", actionNewWindow, as_variant_type),
|
||||
.init("open-config", actionOpenConfig, null),
|
||||
@@ -1121,7 +1121,7 @@ pub const Application = extern struct {
|
||||
.init("reload-config", actionReloadConfig, null),
|
||||
};
|
||||
|
||||
ext.addActions(Self, self, &actions);
|
||||
ext.actions.add(Self, self, &actions);
|
||||
}
|
||||
|
||||
/// Setup our global shortcuts.
|
||||
|
@@ -171,7 +171,7 @@ pub const SplitTree = extern struct {
|
||||
const s_variant_type = glib.ext.VariantType.newFor([:0]const u8);
|
||||
defer s_variant_type.free();
|
||||
|
||||
const actions = [_]ext.Action(Self){
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
// All of these will eventually take a target surface parameter.
|
||||
// For now all our targets originate from the focused surface.
|
||||
.init("new-split", actionNewSplit, s_variant_type),
|
||||
@@ -179,7 +179,7 @@ pub const SplitTree = extern struct {
|
||||
.init("zoom", actionZoom, null),
|
||||
};
|
||||
|
||||
ext.addActionsAsGroup(Self, self, "split-tree", &actions);
|
||||
ext.actions.addAsGroup(Self, self, "split-tree", &actions);
|
||||
}
|
||||
|
||||
/// Create a new split in the given direction from the currently
|
||||
|
@@ -1282,11 +1282,11 @@ pub const Surface = extern struct {
|
||||
}
|
||||
|
||||
fn initActionMap(self: *Self) void {
|
||||
const actions = [_]ext.Action(Self){
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("prompt-title", actionPromptTitle, null),
|
||||
};
|
||||
|
||||
ext.addActionsAsGroup(Self, self, "surface", &actions);
|
||||
ext.actions.addAsGroup(Self, self, "surface", &actions);
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
|
@@ -199,12 +199,12 @@ pub const Tab = extern struct {
|
||||
}
|
||||
|
||||
fn initActionMap(self: *Self) void {
|
||||
const actions = [_]ext.Action(Self){
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("close", actionClose, null),
|
||||
.init("ring-bell", actionRingBell, null),
|
||||
};
|
||||
|
||||
ext.addActionsAsGroup(Self, self, "tab", &actions);
|
||||
ext.actions.addAsGroup(Self, self, "tab", &actions);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
|
@@ -331,7 +331,7 @@ pub const Window = extern struct {
|
||||
|
||||
/// Setup our action map.
|
||||
fn initActionMap(self: *Self) void {
|
||||
const actions = [_]ext.Action(Self){
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("about", actionAbout, null),
|
||||
.init("close", actionClose, null),
|
||||
.init("close-tab", actionCloseTab, null),
|
||||
@@ -351,7 +351,7 @@ pub const Window = extern struct {
|
||||
.init("toggle-inspector", actionToggleInspector, null),
|
||||
};
|
||||
|
||||
ext.addActions(Self, self, &actions);
|
||||
ext.actions.add(Self, self, &actions);
|
||||
}
|
||||
|
||||
/// Winproto backend for this window.
|
||||
|
@@ -12,6 +12,8 @@ const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
pub const actions = @import("ext/actions.zig");
|
||||
|
||||
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
|
||||
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
|
||||
const copy = gobject.boxedCopy(T.getGObjectType(), ptr);
|
||||
@@ -61,149 +63,6 @@ pub fn gValueHolds(value_: ?*const gobject.Value, g_type: gobject.Type) bool {
|
||||
return gobject.typeCheckValueHolds(value, g_type) != 0;
|
||||
}
|
||||
|
||||
/// Check that an action name is valid.
|
||||
///
|
||||
/// Reimplementation of `g_action_name_is_valid()` so that it can be
|
||||
/// used at comptime.
|
||||
///
|
||||
/// See:
|
||||
/// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
||||
fn gActionNameIsValid(name: [:0]const u8) bool {
|
||||
if (name.len == 0) return false;
|
||||
|
||||
for (name) |c| switch (c) {
|
||||
'-' => continue,
|
||||
'.' => continue,
|
||||
'0'...'9' => continue,
|
||||
'a'...'z' => continue,
|
||||
'A'...'Z' => continue,
|
||||
else => return false,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
test "gActionNameIsValid" {
|
||||
try testing.expect(gActionNameIsValid("ring-bell"));
|
||||
try testing.expect(!gActionNameIsValid("ring_bell"));
|
||||
}
|
||||
|
||||
/// Function to create a structure for describing an action.
|
||||
pub fn Action(comptime T: type) type {
|
||||
return struct {
|
||||
pub const Callback = *const fn (*gio.SimpleAction, ?*glib.Variant, *T) callconv(.c) void;
|
||||
|
||||
name: [:0]const u8,
|
||||
callback: Callback,
|
||||
parameter_type: ?*const glib.VariantType,
|
||||
|
||||
/// Function to initialize a new action so that we can comptime check the name.
|
||||
pub fn init(comptime name: [:0]const u8, callback: Callback, parameter_type: ?*const glib.VariantType) @This() {
|
||||
comptime assert(gActionNameIsValid(name));
|
||||
|
||||
return .{
|
||||
.name = name,
|
||||
.callback = callback,
|
||||
.parameter_type = parameter_type,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Add actions to a widget that implements gio.ActionMap.
|
||||
pub fn addActions(comptime T: type, self: *T, actions: []const Action(T)) void {
|
||||
addActionsToMap(T, self, self.as(gio.ActionMap), actions);
|
||||
}
|
||||
|
||||
/// Add actions to the given map.
|
||||
pub fn addActionsToMap(comptime T: type, self: *T, map: *gio.ActionMap, actions: []const Action(T)) void {
|
||||
for (actions) |entry| {
|
||||
assert(gActionNameIsValid(entry.name));
|
||||
const action = gio.SimpleAction.new(
|
||||
entry.name,
|
||||
entry.parameter_type,
|
||||
);
|
||||
defer action.unref();
|
||||
_ = gio.SimpleAction.signals.activate.connect(
|
||||
action,
|
||||
*T,
|
||||
entry.callback,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
map.addAction(action.as(gio.Action));
|
||||
}
|
||||
}
|
||||
|
||||
/// Add actions to a widget that doesn't implement ActionGroup directly.
|
||||
pub fn addActionsAsGroup(comptime T: type, self: *T, comptime name: [:0]const u8, actions: []const Action(T)) void {
|
||||
comptime assert(gActionNameIsValid(name));
|
||||
|
||||
// Collect our actions into a group since we're just a plain widget that
|
||||
// doesn't implement ActionGroup directly.
|
||||
const group = gio.SimpleActionGroup.new();
|
||||
errdefer group.unref();
|
||||
|
||||
addActionsToMap(T, self, group.as(gio.ActionMap), actions);
|
||||
|
||||
self.as(gtk.Widget).insertActionGroup(
|
||||
name,
|
||||
group.as(gio.ActionGroup),
|
||||
);
|
||||
}
|
||||
|
||||
test "adding actions to an object" {
|
||||
// This test requires a connection to an active display environment.
|
||||
if (gtk.initCheck() == 0) return;
|
||||
|
||||
const callbacks = struct {
|
||||
fn callback(_: *gio.SimpleAction, variant_: ?*glib.Variant, self: *gtk.Box) callconv(.c) void {
|
||||
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||
defer i32_variant_type.free();
|
||||
|
||||
const variant = variant_ orelse return;
|
||||
assert(variant.isOfType(i32_variant_type) != 0);
|
||||
|
||||
var value = std.mem.zeroes(gobject.Value);
|
||||
_ = value.init(gobject.ext.types.int);
|
||||
defer value.unset();
|
||||
|
||||
value.setInt(variant.getInt32());
|
||||
|
||||
self.as(gobject.Object).setProperty("spacing", &value);
|
||||
}
|
||||
};
|
||||
|
||||
const box = gtk.Box.new(.vertical, 0);
|
||||
_ = box.as(gobject.Object).refSink();
|
||||
defer box.unref();
|
||||
|
||||
{
|
||||
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||
defer i32_variant_type.free();
|
||||
|
||||
const actions = [_]Action(gtk.Box){
|
||||
.init("test", callbacks.callback, i32_variant_type),
|
||||
};
|
||||
|
||||
addActionsAsGroup(gtk.Box, box, "test", &actions);
|
||||
}
|
||||
|
||||
const expected = std.crypto.random.intRangeAtMost(i32, 1, std.math.maxInt(u31));
|
||||
const parameter = glib.Variant.newInt32(expected);
|
||||
|
||||
try testing.expect(box.as(gtk.Widget).activateActionVariant("test.test", parameter) != 0);
|
||||
|
||||
_ = glib.MainContext.iteration(null, @intFromBool(true));
|
||||
|
||||
var value = std.mem.zeroes(gobject.Value);
|
||||
_ = value.init(gobject.ext.types.int);
|
||||
defer value.unset();
|
||||
|
||||
box.as(gobject.Object).getProperty("spacing", &value);
|
||||
|
||||
try testing.expect(gValueHolds(&value, gobject.ext.types.int));
|
||||
|
||||
const actual = value.getInt();
|
||||
try testing.expectEqual(expected, actual);
|
||||
test {
|
||||
_ = actions;
|
||||
}
|
||||
|
158
src/apprt/gtk-ng/ext/actions.zig
Normal file
158
src/apprt/gtk-ng/ext/actions.zig
Normal file
@@ -0,0 +1,158 @@
|
||||
const std = @import("std");
|
||||
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gValueHolds = @import("../ext.zig").gValueHolds;
|
||||
|
||||
/// Check that an action name is valid.
|
||||
///
|
||||
/// Reimplementation of `g_action_name_is_valid()` so that it can be
|
||||
/// used at comptime.
|
||||
///
|
||||
/// See:
|
||||
/// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
||||
fn gActionNameIsValid(name: [:0]const u8) bool {
|
||||
if (name.len == 0) return false;
|
||||
|
||||
for (name) |c| switch (c) {
|
||||
'-' => continue,
|
||||
'.' => continue,
|
||||
'0'...'9' => continue,
|
||||
'a'...'z' => continue,
|
||||
'A'...'Z' => continue,
|
||||
else => return false,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
test "gActionNameIsValid" {
|
||||
try testing.expect(gActionNameIsValid("ring-bell"));
|
||||
try testing.expect(!gActionNameIsValid("ring_bell"));
|
||||
}
|
||||
|
||||
/// Function to create a structure for describing an action.
|
||||
pub fn Action(comptime T: type) type {
|
||||
return struct {
|
||||
pub const Callback = *const fn (*gio.SimpleAction, ?*glib.Variant, *T) callconv(.c) void;
|
||||
|
||||
name: [:0]const u8,
|
||||
callback: Callback,
|
||||
parameter_type: ?*const glib.VariantType,
|
||||
|
||||
/// Function to initialize a new action so that we can comptime check the name.
|
||||
pub fn init(comptime name: [:0]const u8, callback: Callback, parameter_type: ?*const glib.VariantType) @This() {
|
||||
comptime assert(gActionNameIsValid(name));
|
||||
|
||||
return .{
|
||||
.name = name,
|
||||
.callback = callback,
|
||||
.parameter_type = parameter_type,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Add actions to a widget that implements gio.ActionMap.
|
||||
pub fn add(comptime T: type, self: *T, actions: []const Action(T)) void {
|
||||
addToMap(T, self, self.as(gio.ActionMap), actions);
|
||||
}
|
||||
|
||||
/// Add actions to the given map.
|
||||
pub fn addToMap(comptime T: type, self: *T, map: *gio.ActionMap, actions: []const Action(T)) void {
|
||||
for (actions) |entry| {
|
||||
assert(gActionNameIsValid(entry.name));
|
||||
const action = gio.SimpleAction.new(
|
||||
entry.name,
|
||||
entry.parameter_type,
|
||||
);
|
||||
defer action.unref();
|
||||
_ = gio.SimpleAction.signals.activate.connect(
|
||||
action,
|
||||
*T,
|
||||
entry.callback,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
map.addAction(action.as(gio.Action));
|
||||
}
|
||||
}
|
||||
|
||||
/// Add actions to a widget that doesn't implement ActionGroup directly.
|
||||
pub fn addAsGroup(comptime T: type, self: *T, comptime name: [:0]const u8, actions: []const Action(T)) void {
|
||||
comptime assert(gActionNameIsValid(name));
|
||||
|
||||
// Collect our actions into a group since we're just a plain widget that
|
||||
// doesn't implement ActionGroup directly.
|
||||
const group = gio.SimpleActionGroup.new();
|
||||
errdefer group.unref();
|
||||
|
||||
addToMap(T, self, group.as(gio.ActionMap), actions);
|
||||
|
||||
self.as(gtk.Widget).insertActionGroup(
|
||||
name,
|
||||
group.as(gio.ActionGroup),
|
||||
);
|
||||
}
|
||||
|
||||
test "adding actions to an object" {
|
||||
// This test requires a connection to an active display environment.
|
||||
if (gtk.initCheck() == 0) return;
|
||||
|
||||
const callbacks = struct {
|
||||
fn callback(_: *gio.SimpleAction, variant_: ?*glib.Variant, self: *gtk.Box) callconv(.c) void {
|
||||
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||
defer i32_variant_type.free();
|
||||
|
||||
const variant = variant_ orelse return;
|
||||
assert(variant.isOfType(i32_variant_type) != 0);
|
||||
|
||||
var value = std.mem.zeroes(gobject.Value);
|
||||
_ = value.init(gobject.ext.types.int);
|
||||
defer value.unset();
|
||||
|
||||
value.setInt(variant.getInt32());
|
||||
|
||||
self.as(gobject.Object).setProperty("spacing", &value);
|
||||
}
|
||||
};
|
||||
|
||||
const box = gtk.Box.new(.vertical, 0);
|
||||
_ = box.as(gobject.Object).refSink();
|
||||
defer box.unref();
|
||||
|
||||
{
|
||||
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||
defer i32_variant_type.free();
|
||||
|
||||
const actions = [_]Action(gtk.Box){
|
||||
.init("test", callbacks.callback, i32_variant_type),
|
||||
};
|
||||
|
||||
addAsGroup(gtk.Box, box, "test", &actions);
|
||||
}
|
||||
|
||||
const expected = std.crypto.random.intRangeAtMost(i32, 1, std.math.maxInt(u31));
|
||||
const parameter = glib.Variant.newInt32(expected);
|
||||
|
||||
try testing.expect(box.as(gtk.Widget).activateActionVariant("test.test", parameter) != 0);
|
||||
|
||||
_ = glib.MainContext.iteration(null, @intFromBool(true));
|
||||
|
||||
var value = std.mem.zeroes(gobject.Value);
|
||||
_ = value.init(gobject.ext.types.int);
|
||||
defer value.unset();
|
||||
|
||||
box.as(gobject.Object).getProperty("spacing", &value);
|
||||
|
||||
try testing.expect(gValueHolds(&value, gobject.ext.types.int));
|
||||
|
||||
const actual = value.getInt();
|
||||
try testing.expectEqual(expected, actual);
|
||||
}
|
Reference in New Issue
Block a user