apprt/gtk-ng: show error widget if GLArea fails to initialize
If GTK can't acquire an OpenGL context, this shows a message. Previously, we would only log a warning which was difficult to find. The GUI previously was the default GTK view which showed "Failed to acquire EGL display" which was equally confusing.pull/8390/head
parent
3fb17dc802
commit
4d6269a859
|
|
@ -116,6 +116,11 @@ pub const Application = extern struct {
|
||||||
/// and initialization was successful.
|
/// and initialization was successful.
|
||||||
transient_cgroup_base: ?[]const u8 = null,
|
transient_cgroup_base: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// This is set to true so long as we request a window exactly
|
||||||
|
/// once. This prevents quitting the app before we've shown one
|
||||||
|
/// window.
|
||||||
|
requested_window: bool = false,
|
||||||
|
|
||||||
/// This is set to false internally when the event loop
|
/// This is set to false internally when the event loop
|
||||||
/// should exit and the application should quit. This must
|
/// should exit and the application should quit. This must
|
||||||
/// only be set by the main loop thread.
|
/// only be set by the main loop thread.
|
||||||
|
|
@ -461,7 +466,13 @@ pub const Application = extern struct {
|
||||||
// If the quit timer has expired, quit.
|
// If the quit timer has expired, quit.
|
||||||
if (priv.quit_timer == .expired) break :q true;
|
if (priv.quit_timer == .expired) break :q true;
|
||||||
|
|
||||||
// There's no quit timer running, or it hasn't expired, don't quit.
|
// If we have no windows attached to our app, also quit.
|
||||||
|
if (priv.requested_window and @as(
|
||||||
|
?*glib.List,
|
||||||
|
self.as(gtk.Application).getWindows(),
|
||||||
|
) == null) break :q true;
|
||||||
|
|
||||||
|
// No quit conditions met
|
||||||
break :q false;
|
break :q false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1858,6 +1869,13 @@ const Action = struct {
|
||||||
self: *Application,
|
self: *Application,
|
||||||
parent: ?*CoreSurface,
|
parent: ?*CoreSurface,
|
||||||
) !void {
|
) !void {
|
||||||
|
// Note that we've requested a window at least once. This is used
|
||||||
|
// to trigger quit on no windows. Note I'm not sure if this is REALLY
|
||||||
|
// necessary, but I don't want to risk a bug where on a slow machine
|
||||||
|
// or something we quit immediately after starting up because there
|
||||||
|
// was a delay in the event loop before we created a Window.
|
||||||
|
self.private().requested_window = true;
|
||||||
|
|
||||||
const win = Window.new(self);
|
const win = Window.new(self);
|
||||||
initAndShowWindow(self, win, parent);
|
initAndShowWindow(self, win, parent);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,24 @@ pub const Surface = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const @"error" = struct {
|
||||||
|
pub const name = "error";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
bool,
|
||||||
|
.{
|
||||||
|
.default = false,
|
||||||
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
|
Self,
|
||||||
|
Private,
|
||||||
|
&Private.offset,
|
||||||
|
"error",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const @"font-size-request" = struct {
|
pub const @"font-size-request" = struct {
|
||||||
pub const name = "font-size-request";
|
pub const name = "font-size-request";
|
||||||
const impl = gobject.ext.defineProperty(
|
const impl = gobject.ext.defineProperty(
|
||||||
|
|
@ -472,6 +490,12 @@ pub const Surface = extern struct {
|
||||||
// false by a parent widget.
|
// false by a parent widget.
|
||||||
bell_ringing: bool = false,
|
bell_ringing: bool = false,
|
||||||
|
|
||||||
|
/// True if this surface is in an error state. This is currently
|
||||||
|
/// a simple boolean with no additional information on WHAT the
|
||||||
|
/// error state is, because we don't yet need it or use it. For now,
|
||||||
|
/// if this is true, then it means the terminal is non-functional.
|
||||||
|
@"error": bool = false,
|
||||||
|
|
||||||
/// A weak reference to an inspector window.
|
/// A weak reference to an inspector window.
|
||||||
inspector: ?*InspectorWindow = null,
|
inspector: ?*InspectorWindow = null,
|
||||||
|
|
||||||
|
|
@ -571,6 +595,17 @@ pub const Surface = extern struct {
|
||||||
return @intFromBool(config.@"bell-features".border);
|
return @intFromBool(config.@"bell-features".border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn closureStackChildName(
|
||||||
|
_: *Self,
|
||||||
|
error_: c_int,
|
||||||
|
) callconv(.c) ?[*:0]const u8 {
|
||||||
|
const err = error_ != 0;
|
||||||
|
return if (err)
|
||||||
|
glib.ext.dupeZ(u8, "error")
|
||||||
|
else
|
||||||
|
glib.ext.dupeZ(u8, "terminal");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggleFullscreen(self: *Self) void {
|
pub fn toggleFullscreen(self: *Self) void {
|
||||||
signals.@"toggle-fullscreen".impl.emit(
|
signals.@"toggle-fullscreen".impl.emit(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1540,6 +1575,12 @@ pub const Surface = extern struct {
|
||||||
self.as(gobject.Object).notifyByPspec(properties.@"bell-ringing".impl.param_spec);
|
self.as(gobject.Object).notifyByPspec(properties.@"bell-ringing".impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setError(self: *Self, v: bool) void {
|
||||||
|
const priv = self.private();
|
||||||
|
priv.@"error" = v;
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
fn propConfig(
|
fn propConfig(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
|
|
@ -1592,6 +1633,28 @@ pub const Surface = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn propError(
|
||||||
|
self: *Self,
|
||||||
|
_: *gobject.ParamSpec,
|
||||||
|
_: ?*anyopaque,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.@"error") {
|
||||||
|
// Ensure we have an opaque background. The window will NOT set
|
||||||
|
// this if we have transparency set and we need an opaque
|
||||||
|
// background for the error message to be readable.
|
||||||
|
self.as(gtk.Widget).addCssClass("background");
|
||||||
|
} else {
|
||||||
|
// Regardless of transparency setting, we remove the background
|
||||||
|
// CSS class from this widget. Parent widgets will set it
|
||||||
|
// appropriately (see window.zig for example).
|
||||||
|
self.as(gtk.Widget).removeCssClass("background");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note above: in both cases setting our error view is handled by
|
||||||
|
// a Gtk.Stack visible-child-name binding.
|
||||||
|
}
|
||||||
|
|
||||||
fn propMouseHoverUrl(
|
fn propMouseHoverUrl(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
|
|
@ -1942,8 +2005,11 @@ pub const Surface = extern struct {
|
||||||
// Bell stops ringing if any mouse button is pressed.
|
// Bell stops ringing if any mouse button is pressed.
|
||||||
self.setBellRinging(false);
|
self.setBellRinging(false);
|
||||||
|
|
||||||
// If we don't have focus, grab it.
|
// Get our surface. If we don't have one, ignore this.
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
const core_surface = priv.core_surface orelse return;
|
||||||
|
|
||||||
|
// If we don't have focus, grab it.
|
||||||
const gl_area_widget = priv.gl_area.as(gtk.Widget);
|
const gl_area_widget = priv.gl_area.as(gtk.Widget);
|
||||||
if (gl_area_widget.hasFocus() == 0) {
|
if (gl_area_widget.hasFocus() == 0) {
|
||||||
_ = gl_area_widget.grabFocus();
|
_ = gl_area_widget.grabFocus();
|
||||||
|
|
@ -1951,10 +2017,10 @@ pub const Surface = extern struct {
|
||||||
|
|
||||||
// Report the event
|
// Report the event
|
||||||
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
||||||
const consumed = if (priv.core_surface) |surface| consumed: {
|
const consumed = consumed: {
|
||||||
const gtk_mods = event.getModifierState();
|
const gtk_mods = event.getModifierState();
|
||||||
const mods = gtk_key.translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
break :consumed surface.mouseButtonCallback(
|
break :consumed core_surface.mouseButtonCallback(
|
||||||
.press,
|
.press,
|
||||||
button,
|
button,
|
||||||
mods,
|
mods,
|
||||||
|
|
@ -1962,7 +2028,7 @@ pub const Surface = extern struct {
|
||||||
log.warn("error in key callback err={}", .{err});
|
log.warn("error in key callback err={}", .{err});
|
||||||
break :err false;
|
break :err false;
|
||||||
};
|
};
|
||||||
} else false;
|
};
|
||||||
|
|
||||||
// If a right click isn't consumed, mouseButtonCallback selects the hovered
|
// If a right click isn't consumed, mouseButtonCallback selects the hovered
|
||||||
// word and returns false. We can use this to handle the context menu
|
// word and returns false. We can use this to handle the context menu
|
||||||
|
|
@ -2303,21 +2369,23 @@ pub const Surface = extern struct {
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
log.debug("realize", .{});
|
log.debug("realize", .{});
|
||||||
|
|
||||||
|
// Make the GL area current so we can detect any OpenGL errors. If
|
||||||
|
// we have errors here we can't render and we switch to the error
|
||||||
|
// state.
|
||||||
|
const priv = self.private();
|
||||||
|
priv.gl_area.makeCurrent();
|
||||||
|
if (priv.gl_area.getError()) |err| {
|
||||||
|
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||||
|
log.warn("this error is almost always due to a library, driver, or GTK issue", .{});
|
||||||
|
log.warn("this is a common cause of this issue: https://ghostty.org/docs/help/gtk-opengl-context", .{});
|
||||||
|
self.setError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If we already have an initialized surface then we notify it.
|
// If we already have an initialized surface then we notify it.
|
||||||
// If we don't, we'll initialize it on the first resize so we have
|
// If we don't, we'll initialize it on the first resize so we have
|
||||||
// our proper initial dimensions.
|
// our proper initial dimensions.
|
||||||
const priv = self.private();
|
|
||||||
if (priv.core_surface) |v| realize: {
|
if (priv.core_surface) |v| realize: {
|
||||||
// We need to make the context current so we can call GL functions.
|
|
||||||
// This is required for all surface operations.
|
|
||||||
priv.gl_area.makeCurrent();
|
|
||||||
if (priv.gl_area.getError()) |err| {
|
|
||||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
|
||||||
log.warn("this error is usually due to a driver or gtk bug", .{});
|
|
||||||
log.warn("this is a common cause of this issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/4950", .{});
|
|
||||||
break :realize;
|
|
||||||
}
|
|
||||||
|
|
||||||
v.renderer.displayRealized() catch |err| {
|
v.renderer.displayRealized() catch |err| {
|
||||||
log.warn("core displayRealized failed err={}", .{err});
|
log.warn("core displayRealized failed err={}", .{err});
|
||||||
break :realize;
|
break :realize;
|
||||||
|
|
@ -2662,11 +2730,13 @@ pub const Surface = extern struct {
|
||||||
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
||||||
class.bindTemplateCallback("context_menu_closed", &contextMenuClosed);
|
class.bindTemplateCallback("context_menu_closed", &contextMenuClosed);
|
||||||
class.bindTemplateCallback("notify_config", &propConfig);
|
class.bindTemplateCallback("notify_config", &propConfig);
|
||||||
|
class.bindTemplateCallback("notify_error", &propError);
|
||||||
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
||||||
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
||||||
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
|
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
|
||||||
class.bindTemplateCallback("notify_bell_ringing", &propBellRinging);
|
class.bindTemplateCallback("notify_bell_ringing", &propBellRinging);
|
||||||
class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown);
|
class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown);
|
||||||
|
class.bindTemplateCallback("stack_child_name", &closureStackChildName);
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
|
|
@ -2674,6 +2744,7 @@ pub const Surface = extern struct {
|
||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
properties.@"child-exited".impl,
|
properties.@"child-exited".impl,
|
||||||
properties.@"default-size".impl,
|
properties.@"default-size".impl,
|
||||||
|
properties.@"error".impl,
|
||||||
properties.@"font-size-request".impl,
|
properties.@"font-size-request".impl,
|
||||||
properties.focused.impl,
|
properties.focused.impl,
|
||||||
properties.@"min-size".impl,
|
properties.@"min-size".impl,
|
||||||
|
|
|
||||||
|
|
@ -8,146 +8,172 @@ template $GhosttySurface: Adw.Bin {
|
||||||
|
|
||||||
notify::bell-ringing => $notify_bell_ringing();
|
notify::bell-ringing => $notify_bell_ringing();
|
||||||
notify::config => $notify_config();
|
notify::config => $notify_config();
|
||||||
|
notify::error => $notify_error();
|
||||||
notify::mouse-hover-url => $notify_mouse_hover_url();
|
notify::mouse-hover-url => $notify_mouse_hover_url();
|
||||||
notify::mouse-hidden => $notify_mouse_hidden();
|
notify::mouse-hidden => $notify_mouse_hidden();
|
||||||
notify::mouse-shape => $notify_mouse_shape();
|
notify::mouse-shape => $notify_mouse_shape();
|
||||||
|
|
||||||
Overlay {
|
Stack {
|
||||||
focusable: false;
|
StackPage {
|
||||||
focus-on-click: false;
|
name: "terminal";
|
||||||
|
|
||||||
child: Box {
|
child: Overlay {
|
||||||
hexpand: true;
|
focusable: false;
|
||||||
vexpand: true;
|
focus-on-click: false;
|
||||||
|
|
||||||
GLArea gl_area {
|
child: Box {
|
||||||
realize => $gl_realize();
|
hexpand: true;
|
||||||
unrealize => $gl_unrealize();
|
vexpand: true;
|
||||||
render => $gl_render();
|
|
||||||
resize => $gl_resize();
|
|
||||||
hexpand: true;
|
|
||||||
vexpand: true;
|
|
||||||
focusable: true;
|
|
||||||
focus-on-click: true;
|
|
||||||
has-stencil-buffer: false;
|
|
||||||
has-depth-buffer: false;
|
|
||||||
allowed-apis: gl;
|
|
||||||
}
|
|
||||||
|
|
||||||
PopoverMenu context_menu {
|
GLArea gl_area {
|
||||||
closed => $context_menu_closed();
|
realize => $gl_realize();
|
||||||
menu-model: context_menu_model;
|
unrealize => $gl_unrealize();
|
||||||
flags: nested;
|
render => $gl_render();
|
||||||
halign: start;
|
resize => $gl_resize();
|
||||||
has-arrow: false;
|
hexpand: true;
|
||||||
}
|
vexpand: true;
|
||||||
};
|
focusable: true;
|
||||||
|
focus-on-click: true;
|
||||||
|
has-stencil-buffer: false;
|
||||||
|
has-depth-buffer: false;
|
||||||
|
allowed-apis: gl;
|
||||||
|
}
|
||||||
|
|
||||||
[overlay]
|
PopoverMenu context_menu {
|
||||||
ProgressBar progress_bar_overlay {
|
closed => $context_menu_closed();
|
||||||
styles [
|
menu-model: context_menu_model;
|
||||||
"osd",
|
flags: nested;
|
||||||
]
|
halign: start;
|
||||||
|
has-arrow: false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
visible: false;
|
[overlay]
|
||||||
halign: fill;
|
ProgressBar progress_bar_overlay {
|
||||||
valign: start;
|
styles [
|
||||||
|
"osd",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: fill;
|
||||||
|
valign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
// The "border" bell feature is implemented here as an overlay rather than
|
||||||
|
// just adding a border to the GLArea or other widget for two reasons.
|
||||||
|
// First, adding a border to an existing widget causes a resize of the
|
||||||
|
// widget which undesirable side effects. Second, we can make it reactive
|
||||||
|
// here in the blueprint with relatively little code.
|
||||||
|
Revealer {
|
||||||
|
reveal-child: bind $should_border_be_shown(template.config, template.bell-ringing) as <bool>;
|
||||||
|
transition-type: crossfade;
|
||||||
|
transition-duration: 500;
|
||||||
|
|
||||||
|
Box bell_overlay {
|
||||||
|
styles [
|
||||||
|
"bell-overlay",
|
||||||
|
]
|
||||||
|
|
||||||
|
halign: fill;
|
||||||
|
valign: fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
$GhosttySurfaceChildExited child_exited_overlay {
|
||||||
|
visible: bind template.child-exited;
|
||||||
|
close-request => $child_exited_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
$GhosttyResizeOverlay resize_overlay {}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
Label url_left {
|
||||||
|
styles [
|
||||||
|
"background",
|
||||||
|
"url-overlay",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: start;
|
||||||
|
valign: end;
|
||||||
|
label: bind template.mouse-hover-url;
|
||||||
|
|
||||||
|
EventControllerMotion url_ec_motion {
|
||||||
|
enter => $url_mouse_enter();
|
||||||
|
leave => $url_mouse_leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
Label url_right {
|
||||||
|
styles [
|
||||||
|
"background",
|
||||||
|
"url-overlay",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: end;
|
||||||
|
valign: end;
|
||||||
|
label: bind template.mouse-hover-url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event controllers for interactivity
|
||||||
|
EventControllerFocus {
|
||||||
|
enter => $focus_enter();
|
||||||
|
leave => $focus_leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventControllerKey {
|
||||||
|
key-pressed => $key_pressed();
|
||||||
|
key-released => $key_released();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventControllerMotion {
|
||||||
|
motion => $mouse_motion();
|
||||||
|
leave => $mouse_leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventControllerScroll {
|
||||||
|
scroll => $scroll();
|
||||||
|
scroll-begin => $scroll_begin();
|
||||||
|
scroll-end => $scroll_end();
|
||||||
|
flags: both_axes;
|
||||||
|
}
|
||||||
|
|
||||||
|
GestureClick {
|
||||||
|
pressed => $mouse_down();
|
||||||
|
released => $mouse_up();
|
||||||
|
button: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DropTarget drop_target {
|
||||||
|
drop => $drop();
|
||||||
|
actions: copy;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[overlay]
|
StackPage {
|
||||||
// The "border" bell feature is implemented here as an overlay rather than
|
name: "error";
|
||||||
// just adding a border to the GLArea or other widget for two reasons.
|
|
||||||
// First, adding a border to an existing widget causes a resize of the
|
|
||||||
// widget which undesirable side effects. Second, we can make it reactive
|
|
||||||
// here in the blueprint with relatively little code.
|
|
||||||
Revealer {
|
|
||||||
reveal-child: bind $should_border_be_shown(template.config, template.bell-ringing) as <bool>;
|
|
||||||
transition-type: crossfade;
|
|
||||||
transition-duration: 500;
|
|
||||||
|
|
||||||
Box bell_overlay {
|
child: Adw.StatusPage {
|
||||||
styles [
|
icon-name: "computer-fail-symbolic";
|
||||||
"bell-overlay",
|
title: _("Oh, no.");
|
||||||
]
|
description: _("Unable to acquire an OpenGL context for rendering.");
|
||||||
|
|
||||||
halign: fill;
|
child: LinkButton {
|
||||||
valign: fill;
|
label: "https://ghostty.org/docs/help/gtk-opengl-context";
|
||||||
}
|
uri: "https://ghostty.org/docs/help/gtk-opengl-context";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[overlay]
|
// The order matters here: we can only set this after the stack
|
||||||
$GhosttySurfaceChildExited child_exited_overlay {
|
// pages above have been created.
|
||||||
visible: bind template.child-exited;
|
visible-child-name: bind $stack_child_name(template.error) as <string>;
|
||||||
close-request => $child_exited_close();
|
|
||||||
}
|
|
||||||
|
|
||||||
[overlay]
|
|
||||||
$GhosttyResizeOverlay resize_overlay {}
|
|
||||||
|
|
||||||
[overlay]
|
|
||||||
Label url_left {
|
|
||||||
styles [
|
|
||||||
"background",
|
|
||||||
"url-overlay",
|
|
||||||
]
|
|
||||||
|
|
||||||
visible: false;
|
|
||||||
halign: start;
|
|
||||||
valign: end;
|
|
||||||
label: bind template.mouse-hover-url;
|
|
||||||
|
|
||||||
EventControllerMotion url_ec_motion {
|
|
||||||
enter => $url_mouse_enter();
|
|
||||||
leave => $url_mouse_leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[overlay]
|
|
||||||
Label url_right {
|
|
||||||
styles [
|
|
||||||
"background",
|
|
||||||
"url-overlay",
|
|
||||||
]
|
|
||||||
|
|
||||||
visible: false;
|
|
||||||
halign: end;
|
|
||||||
valign: end;
|
|
||||||
label: bind template.mouse-hover-url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event controllers for interactivity
|
|
||||||
EventControllerFocus {
|
|
||||||
enter => $focus_enter();
|
|
||||||
leave => $focus_leave();
|
|
||||||
}
|
|
||||||
|
|
||||||
EventControllerKey {
|
|
||||||
key-pressed => $key_pressed();
|
|
||||||
key-released => $key_released();
|
|
||||||
}
|
|
||||||
|
|
||||||
EventControllerMotion {
|
|
||||||
motion => $mouse_motion();
|
|
||||||
leave => $mouse_leave();
|
|
||||||
}
|
|
||||||
|
|
||||||
EventControllerScroll {
|
|
||||||
scroll => $scroll();
|
|
||||||
scroll-begin => $scroll_begin();
|
|
||||||
scroll-end => $scroll_end();
|
|
||||||
flags: both_axes;
|
|
||||||
}
|
|
||||||
|
|
||||||
GestureClick {
|
|
||||||
pressed => $mouse_down();
|
|
||||||
released => $mouse_up();
|
|
||||||
button: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DropTarget drop_target {
|
|
||||||
drop => $drop();
|
|
||||||
actions: copy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue