mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
apprt/gtk-ng: surface progress bar
This commit is contained in:
@ -481,6 +481,8 @@ pub const Application = extern struct {
|
|||||||
|
|
||||||
.quit_timer => try Action.quitTimer(self, value),
|
.quit_timer => try Action.quitTimer(self, value),
|
||||||
|
|
||||||
|
.progress_report => return Action.progressReport(target, value),
|
||||||
|
|
||||||
.render => Action.render(self, target),
|
.render => Action.render(self, target),
|
||||||
|
|
||||||
.set_title => Action.setTitle(target, value),
|
.set_title => Action.setTitle(target, value),
|
||||||
@ -528,7 +530,6 @@ pub const Application = extern struct {
|
|||||||
.check_for_updates,
|
.check_for_updates,
|
||||||
.undo,
|
.undo,
|
||||||
.redo,
|
.redo,
|
||||||
.progress_report,
|
|
||||||
=> {
|
=> {
|
||||||
log.warn("unimplemented action={}", .{action});
|
log.warn("unimplemented action={}", .{action});
|
||||||
return false;
|
return false;
|
||||||
@ -1117,6 +1118,19 @@ const Action = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn progressReport(
|
||||||
|
target: apprt.Target,
|
||||||
|
value: terminal.osc.Command.ProgressReport,
|
||||||
|
) bool {
|
||||||
|
return switch (target) {
|
||||||
|
.app => false,
|
||||||
|
.surface => |v| surface: {
|
||||||
|
v.rt_surface.surface.setProgressReport(value);
|
||||||
|
break :surface true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(_: *Application, target: apprt.Target) void {
|
pub fn render(_: *Application, target: apprt.Target) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => {},
|
.app => {},
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const adw = @import("adw");
|
const adw = @import("adw");
|
||||||
const gdk = @import("gdk");
|
const gdk = @import("gdk");
|
||||||
@ -308,9 +309,13 @@ pub const Surface = extern struct {
|
|||||||
/// True when the child has exited.
|
/// True when the child has exited.
|
||||||
child_exited: bool = false,
|
child_exited: bool = false,
|
||||||
|
|
||||||
|
// Progress bar
|
||||||
|
progress_bar_timer: ?c_uint = null,
|
||||||
|
|
||||||
// Template binds
|
// Template binds
|
||||||
child_exited_overlay: *ChildExited,
|
child_exited_overlay: *ChildExited,
|
||||||
drop_target: *gtk.DropTarget,
|
drop_target: *gtk.DropTarget,
|
||||||
|
progress_bar_overlay: *gtk.ProgressBar,
|
||||||
|
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
@ -338,6 +343,93 @@ pub const Surface = extern struct {
|
|||||||
priv.gl_area.queueRender();
|
priv.gl_area.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the current progress report state.
|
||||||
|
pub fn setProgressReport(
|
||||||
|
self: *Self,
|
||||||
|
value: terminal.osc.Command.ProgressReport,
|
||||||
|
) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// No matter what, we stop the timer because if we're removing
|
||||||
|
// then we're done and otherwise we restart it.
|
||||||
|
if (priv.progress_bar_timer) |timer| {
|
||||||
|
if (glib.Source.remove(timer) == 0) {
|
||||||
|
log.warn("unable to remove progress bar timer", .{});
|
||||||
|
}
|
||||||
|
priv.progress_bar_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress_bar = priv.progress_bar_overlay;
|
||||||
|
switch (value.state) {
|
||||||
|
// Remove the progress bar
|
||||||
|
.remove => {
|
||||||
|
progress_bar.as(gtk.Widget).setVisible(@intFromBool(false));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set the progress bar to a fixed value if one was provided, otherwise pulse.
|
||||||
|
// Remove the `error` CSS class so that the progress bar shows as normal.
|
||||||
|
.set => {
|
||||||
|
progress_bar.as(gtk.Widget).removeCssClass("error");
|
||||||
|
if (value.progress) |progress| {
|
||||||
|
progress_bar.setFraction(computeFraction(progress));
|
||||||
|
} else {
|
||||||
|
progress_bar.pulse();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set the progress bar to a fixed value if one was provided, otherwise pulse.
|
||||||
|
// Set the `error` CSS class so that the progress bar shows as an error color.
|
||||||
|
.@"error" => {
|
||||||
|
progress_bar.as(gtk.Widget).addCssClass("error");
|
||||||
|
if (value.progress) |progress| {
|
||||||
|
progress_bar.setFraction(computeFraction(progress));
|
||||||
|
} else {
|
||||||
|
progress_bar.pulse();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// The state of progress is unknown, so pulse the progress bar to
|
||||||
|
// indicate that things are still happening.
|
||||||
|
.indeterminate => {
|
||||||
|
progress_bar.pulse();
|
||||||
|
},
|
||||||
|
|
||||||
|
// If a progress value was provided, set the progress bar to that value.
|
||||||
|
// Don't pulse the progress bar as that would indicate that things were
|
||||||
|
// happening. Otherwise this is mainly used to keep the progress bar on
|
||||||
|
// screen instead of timing out.
|
||||||
|
.pause => {
|
||||||
|
if (value.progress) |progress| {
|
||||||
|
progress_bar.setFraction(computeFraction(progress));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume all states lead to visibility
|
||||||
|
assert(value.state != .remove);
|
||||||
|
progress_bar.as(gtk.Widget).setVisible(@intFromBool(true));
|
||||||
|
|
||||||
|
// Start our timer to remove bad actor programs that stall
|
||||||
|
// the progress bar.
|
||||||
|
const progress_bar_timeout_seconds = 15;
|
||||||
|
assert(priv.progress_bar_timer == null);
|
||||||
|
priv.progress_bar_timer = glib.timeoutAdd(
|
||||||
|
progress_bar_timeout_seconds * std.time.ms_per_s,
|
||||||
|
progressBarTimer,
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The progress bar hasn't been updated by the TUI recently, remove it.
|
||||||
|
fn progressBarTimer(ud: ?*anyopaque) callconv(.c) c_int {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ud.?));
|
||||||
|
const priv = self.private();
|
||||||
|
priv.progress_bar_timer = null;
|
||||||
|
self.setProgressReport(.{ .state = .remove });
|
||||||
|
return @intFromBool(glib.SOURCE_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
/// Key press event (press or release).
|
/// Key press event (press or release).
|
||||||
///
|
///
|
||||||
/// At a high level, we want to construct an `input.KeyEvent` and
|
/// At a high level, we want to construct an `input.KeyEvent` and
|
||||||
@ -873,6 +965,12 @@ pub const Surface = extern struct {
|
|||||||
v.unref();
|
v.unref();
|
||||||
priv.config = null;
|
priv.config = null;
|
||||||
}
|
}
|
||||||
|
if (priv.progress_bar_timer) |timer| {
|
||||||
|
if (glib.Source.remove(timer) == 0) {
|
||||||
|
log.warn("unable to remove progress bar timer", .{});
|
||||||
|
}
|
||||||
|
priv.progress_bar_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
gtk.Widget.disposeTemplate(
|
gtk.Widget.disposeTemplate(
|
||||||
self.as(gtk.Widget),
|
self.as(gtk.Widget),
|
||||||
@ -1833,6 +1931,7 @@ pub const Surface = extern struct {
|
|||||||
class.bindTemplateChildPrivate("url_left", .{});
|
class.bindTemplateChildPrivate("url_left", .{});
|
||||||
class.bindTemplateChildPrivate("url_right", .{});
|
class.bindTemplateChildPrivate("url_right", .{});
|
||||||
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
||||||
|
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
|
||||||
class.bindTemplateChildPrivate("resize_overlay", .{});
|
class.bindTemplateChildPrivate("resize_overlay", .{});
|
||||||
class.bindTemplateChildPrivate("drop_target", .{});
|
class.bindTemplateChildPrivate("drop_target", .{});
|
||||||
class.bindTemplateChildPrivate("im_context", .{});
|
class.bindTemplateChildPrivate("im_context", .{});
|
||||||
@ -2221,3 +2320,16 @@ fn g_value_holds(value_: ?*gobject.Value, g_type: gobject.Type) bool {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute a fraction [0.0, 1.0] from the supplied progress, which is clamped
|
||||||
|
/// to [0, 100].
|
||||||
|
fn computeFraction(progress: u8) f64 {
|
||||||
|
return @as(f64, @floatFromInt(std.math.clamp(progress, 0, 100))) / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeFraction" {
|
||||||
|
try std.testing.expectEqual(1.0, computeFraction(100));
|
||||||
|
try std.testing.expectEqual(1.0, computeFraction(255));
|
||||||
|
try std.testing.expectEqual(0.0, computeFraction(0));
|
||||||
|
try std.testing.expectEqual(0.5, computeFraction(50));
|
||||||
|
}
|
||||||
|
@ -84,3 +84,12 @@ label.url-overlay.right {
|
|||||||
/* after GTK 4.16 is a requirement, switch to the following:
|
/* after GTK 4.16 is a requirement, switch to the following:
|
||||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
|
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Surface
|
||||||
|
*/
|
||||||
|
.surface progressbar.error trough progress {
|
||||||
|
background-color: rgb(192, 28, 40);
|
||||||
|
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||||
|
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent); */
|
||||||
|
}
|
||||||
|
@ -2,6 +2,10 @@ using Gtk 4.0;
|
|||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $GhosttySurface: Adw.Bin {
|
template $GhosttySurface: Adw.Bin {
|
||||||
|
styles [
|
||||||
|
"surface",
|
||||||
|
]
|
||||||
|
|
||||||
notify::config => $notify_config();
|
notify::config => $notify_config();
|
||||||
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();
|
||||||
@ -25,6 +29,17 @@ template $GhosttySurface: Adw.Bin {
|
|||||||
use-es: false;
|
use-es: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
ProgressBar progress_bar_overlay {
|
||||||
|
styles [
|
||||||
|
"osd",
|
||||||
|
]
|
||||||
|
|
||||||
|
visible: false;
|
||||||
|
halign: fill;
|
||||||
|
valign: start;
|
||||||
|
}
|
||||||
|
|
||||||
[overlay]
|
[overlay]
|
||||||
$GhosttySurfaceChildExited child_exited_overlay {
|
$GhosttySurfaceChildExited child_exited_overlay {
|
||||||
visible: bind template.child-exited;
|
visible: bind template.child-exited;
|
||||||
|
Reference in New Issue
Block a user