mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
apprt/gtk-ng: mouse over link to show tooltip
This commit is contained in:
@ -1316,9 +1316,7 @@ fn mouseRefreshLinks(
|
|||||||
break :link .{ null, false };
|
break :link .{ null, false };
|
||||||
};
|
};
|
||||||
break :link .{
|
break :link .{
|
||||||
.{
|
.{ .url = try alloc.dupeZ(u8, uri) },
|
||||||
.url = uri,
|
|
||||||
},
|
|
||||||
self.config.link_previews != .false,
|
self.config.link_previews != .false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -507,7 +507,7 @@ pub const MouseVisibility = enum(c_int) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const MouseOverLink = struct {
|
pub const MouseOverLink = struct {
|
||||||
url: []const u8,
|
url: [:0]const u8,
|
||||||
|
|
||||||
// Sync with: ghostty_action_mouse_over_link_s
|
// Sync with: ghostty_action_mouse_over_link_s
|
||||||
pub const C = extern struct {
|
pub const C = extern struct {
|
||||||
|
@ -463,6 +463,7 @@ pub const Application = extern struct {
|
|||||||
value.config,
|
value.config,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.mouse_over_link => Action.mouseOverLink(target, value),
|
||||||
.mouse_shape => Action.mouseShape(target, value),
|
.mouse_shape => Action.mouseShape(target, value),
|
||||||
.mouse_visibility => Action.mouseVisibility(target, value),
|
.mouse_visibility => Action.mouseVisibility(target, value),
|
||||||
|
|
||||||
@ -505,7 +506,6 @@ pub const Application = extern struct {
|
|||||||
.present_terminal,
|
.present_terminal,
|
||||||
.initial_size,
|
.initial_size,
|
||||||
.size_limit,
|
.size_limit,
|
||||||
.mouse_over_link,
|
|
||||||
.toggle_tab_overview,
|
.toggle_tab_overview,
|
||||||
.toggle_split_zoom,
|
.toggle_split_zoom,
|
||||||
.toggle_window_decorations,
|
.toggle_window_decorations,
|
||||||
@ -1015,6 +1015,25 @@ const Action = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mouseOverLink(
|
||||||
|
target: apprt.Target,
|
||||||
|
value: apprt.action.MouseOverLink,
|
||||||
|
) void {
|
||||||
|
switch (target) {
|
||||||
|
.app => log.warn("mouse over link to app is unexpected", .{}),
|
||||||
|
.surface => |surface| {
|
||||||
|
var v = gobject.ext.Value.new([:0]const u8);
|
||||||
|
if (value.url.len > 0) gobject.ext.Value.set(&v, value.url);
|
||||||
|
defer v.unset();
|
||||||
|
gobject.Object.setProperty(
|
||||||
|
surface.rt_surface.gobj().as(gobject.Object),
|
||||||
|
"mouse-hover-url",
|
||||||
|
&v,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mouseShape(
|
pub fn mouseShape(
|
||||||
target: apprt.Target,
|
target: apprt.Target,
|
||||||
shape: terminal.MouseShape,
|
shape: terminal.MouseShape,
|
||||||
|
@ -94,6 +94,23 @@ pub const Surface = extern struct {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const @"mouse-hover-url" = struct {
|
||||||
|
pub const name = "mouse-hover-url";
|
||||||
|
pub const get = impl.get;
|
||||||
|
pub const set = impl.set;
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.nick = "Mouse Hover URL",
|
||||||
|
.blurb = "The URL the mouse is currently hovering over (if any).",
|
||||||
|
.default = null,
|
||||||
|
.accessor = C.privateStringFieldAccessor("mouse_hover_url"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const pwd = struct {
|
pub const pwd = struct {
|
||||||
pub const name = "pwd";
|
pub const name = "pwd";
|
||||||
pub const get = impl.get;
|
pub const get = impl.get;
|
||||||
@ -162,6 +179,9 @@ pub const Surface = extern struct {
|
|||||||
/// Whether the mouse should be hidden or not as requested externally.
|
/// Whether the mouse should be hidden or not as requested externally.
|
||||||
mouse_hidden: bool = false,
|
mouse_hidden: bool = false,
|
||||||
|
|
||||||
|
/// The URL that the mouse is currently hovering over.
|
||||||
|
mouse_hover_url: ?[:0]const u8 = null,
|
||||||
|
|
||||||
/// The current working directory. This has to be reported externally,
|
/// The current working directory. This has to be reported externally,
|
||||||
/// usually by shell integration which then talks to libghostty
|
/// usually by shell integration which then talks to libghostty
|
||||||
/// which triggers this property.
|
/// which triggers this property.
|
||||||
@ -170,10 +190,18 @@ pub const Surface = extern struct {
|
|||||||
/// The title of this surface, if any has been set.
|
/// The title of this surface, if any has been set.
|
||||||
title: ?[:0]const u8 = null,
|
title: ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
/// The overlay we use for things such as the URL hover label
|
||||||
|
/// or resize box. Bound from the template.
|
||||||
|
overlay: *gtk.Overlay = undefined,
|
||||||
|
|
||||||
/// The GLAarea that renders the actual surface. This is a binding
|
/// The GLAarea that renders the actual surface. This is a binding
|
||||||
/// to the template so it doesn't have to be unrefed manually.
|
/// to the template so it doesn't have to be unrefed manually.
|
||||||
gl_area: *gtk.GLArea = undefined,
|
gl_area: *gtk.GLArea = undefined,
|
||||||
|
|
||||||
|
/// The labels for the left/right sides of the URL hover tooltip.
|
||||||
|
url_left: *gtk.Label = undefined,
|
||||||
|
url_right: *gtk.Label = undefined,
|
||||||
|
|
||||||
/// The apprt Surface.
|
/// The apprt Surface.
|
||||||
rt_surface: ApprtSurface = undefined,
|
rt_surface: ApprtSurface = undefined,
|
||||||
|
|
||||||
@ -809,6 +837,13 @@ pub const Surface = extern struct {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Some property signals
|
// Some property signals
|
||||||
|
_ = gobject.Object.signals.notify.connect(
|
||||||
|
self,
|
||||||
|
?*anyopaque,
|
||||||
|
&propMouseHoverUrl,
|
||||||
|
null,
|
||||||
|
.{ .detail = "mouse-hover-url" },
|
||||||
|
);
|
||||||
_ = gobject.Object.signals.notify.connect(
|
_ = gobject.Object.signals.notify.connect(
|
||||||
self,
|
self,
|
||||||
?*anyopaque,
|
?*anyopaque,
|
||||||
@ -823,6 +858,41 @@ pub const Surface = extern struct {
|
|||||||
null,
|
null,
|
||||||
.{ .detail = "mouse-shape" },
|
.{ .detail = "mouse-shape" },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Some other initialization steps
|
||||||
|
self.initUrlOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initUrlOverlay(self: *Self) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const overlay = priv.overlay;
|
||||||
|
const url_left = priv.url_left.as(gtk.Widget);
|
||||||
|
const url_right = priv.url_right.as(gtk.Widget);
|
||||||
|
|
||||||
|
// Add the url label to the overlay
|
||||||
|
overlay.addOverlay(url_left);
|
||||||
|
overlay.addOverlay(url_right);
|
||||||
|
|
||||||
|
// Setup a motion controller to handle moving the label
|
||||||
|
// to avoid the mouse.
|
||||||
|
const ec_motion = gtk.EventControllerMotion.new();
|
||||||
|
errdefer ec_motion.unref();
|
||||||
|
url_left.addController(ec_motion.as(gtk.EventController));
|
||||||
|
errdefer url_left.removeController(ec_motion.as(gtk.EventController));
|
||||||
|
_ = gtk.EventControllerMotion.signals.enter.connect(
|
||||||
|
ec_motion,
|
||||||
|
*Self,
|
||||||
|
ecUrlMouseEnter,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.EventControllerMotion.signals.leave.connect(
|
||||||
|
ec_motion,
|
||||||
|
*Self,
|
||||||
|
ecUrlMouseLeave,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.C) void {
|
||||||
@ -863,6 +933,10 @@ pub const Surface = extern struct {
|
|||||||
priv.core_surface = null;
|
priv.core_surface = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priv.mouse_hover_url) |v| {
|
||||||
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.mouse_hover_url = null;
|
||||||
|
}
|
||||||
if (priv.pwd) |v| {
|
if (priv.pwd) |v| {
|
||||||
glib.free(@constCast(@ptrCast(v)));
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
priv.pwd = null;
|
priv.pwd = null;
|
||||||
@ -886,6 +960,16 @@ pub const Surface = extern struct {
|
|||||||
return self.private().title;
|
return self.private().title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn propMouseHoverUrl(
|
||||||
|
self: *Self,
|
||||||
|
_: *gobject.ParamSpec,
|
||||||
|
_: ?*anyopaque,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const visible = if (priv.mouse_hover_url) |v| v.len > 0 else false;
|
||||||
|
priv.url_left.as(gtk.Widget).setVisible(if (visible) 1 else 0);
|
||||||
|
}
|
||||||
|
|
||||||
fn propMouseHidden(
|
fn propMouseHidden(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
@ -1539,6 +1623,26 @@ pub const Surface = extern struct {
|
|||||||
priv.core_surface = surface;
|
priv.core_surface = surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ecUrlMouseEnter(
|
||||||
|
_: *gtk.EventControllerMotion,
|
||||||
|
_: f64,
|
||||||
|
_: f64,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const right = priv.url_right.as(gtk.Widget);
|
||||||
|
right.setVisible(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ecUrlMouseLeave(
|
||||||
|
_: *gtk.EventControllerMotion,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const right = priv.url_right.as(gtk.Widget);
|
||||||
|
right.setVisible(0);
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
@ -1561,13 +1665,17 @@ pub const Surface = extern struct {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
|
class.bindTemplateChildPrivate("overlay", .{});
|
||||||
class.bindTemplateChildPrivate("gl_area", .{});
|
class.bindTemplateChildPrivate("gl_area", .{});
|
||||||
|
class.bindTemplateChildPrivate("url_left", .{});
|
||||||
|
class.bindTemplateChildPrivate("url_right", .{});
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
properties.@"mouse-shape".impl,
|
properties.@"mouse-shape".impl,
|
||||||
properties.@"mouse-hidden".impl,
|
properties.@"mouse-hidden".impl,
|
||||||
|
properties.@"mouse-hover-url".impl,
|
||||||
properties.pwd.impl,
|
properties.pwd.impl,
|
||||||
properties.title.impl,
|
properties.title.impl,
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,21 @@
|
|||||||
* https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles
|
* https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
label {
|
label.url-overlay {
|
||||||
color: red;
|
padding: 4px 8px 4px 8px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-color: #555555;
|
||||||
|
outline-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.url-overlay:hover {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.url-overlay.left {
|
||||||
|
border-radius: 0px 6px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.url-overlay.right {
|
||||||
|
border-radius: 6px 0px 0px 0px;
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,43 @@ using Gtk 4.0;
|
|||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $GhosttySurface: Adw.Bin {
|
template $GhosttySurface: Adw.Bin {
|
||||||
Overlay {
|
// We need to wrap our Overlay one more time because if you bind a
|
||||||
focusable: false;
|
// direct child of your widget to a property, it will double free:
|
||||||
focus-on-click: false;
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/847571a1e314aba79260e4ef282e2ed9ba91a0d9/gtk/gtkwidget.c#L11423-11425
|
||||||
|
Adw.Bin {
|
||||||
|
Overlay overlay {
|
||||||
|
focusable: false;
|
||||||
|
focus-on-click: false;
|
||||||
|
|
||||||
GLArea gl_area {
|
GLArea gl_area {
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
focusable: true;
|
focusable: true;
|
||||||
focus-on-click: true;
|
focus-on-click: true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The label that shows the currently hovered URL.
|
||||||
|
Label url_left {
|
||||||
|
styles [
|
||||||
|
"url-overlay",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: start;
|
||||||
|
valign: end;
|
||||||
|
label: bind template.mouse-hover-url;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label url_right {
|
||||||
|
styles [
|
||||||
|
"url-overlay",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: end;
|
||||||
|
valign: end;
|
||||||
|
label: bind template.mouse-hover-url;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user