feat(apprt-gtk): port the GTK4/libadwaita app runtime
Ports ZIG/src/apprt/gtk into ghostty-apprt-gtk: every GObject class (application, window, tab, split tree, surface + scrolled window, child-exited banner, search/key-state/resize overlays, command palette, global shortcuts, all dialogs, config wrapper) as glib subclasses with the same GType names, properties, signals and template children; GDK key translation; X11 and Wayland window-protocol helpers (blur, CSD, quick terminal via gtk4-layer-shell); single-instance D-Bus IPC; gsettings; cgroup scope creation. build.rs compiles Ghostty's blueprints with blueprint-compiler and bundles UI, CSS and icons into a gresource, as Ghostty's build does. The core is not ported yet, so the crate defines the boundary it needs in src/core.rs (CoreApp, CoreSurface, RtApp, RtSurface, Waker); the spec is in docs/rewrite/notes/ghostty-apprt-gtk.md. ghostty-core will implement these traits (the crate no longer depends on core to avoid a cycle). examples/demo.rs runs the app with a dummy core that clears the GLArea; it launches on Wayland without GTK criticals. Stubs/open items: toggle_visibility, float_window, secure_input and close_all_windows return false; inspector is a no-op; X11/Wayland protocol code is untested against real compositors; gtk4-rs doesn't expose the GLArea framebuffer id, so the renderer must query it. Also registers ghostty-apprt and ghostty-cli in workspace.dependencies. Test Plan: - cargo test -p ghostty-apprt-gtk (36 tests) - DEMO_QUIT_AFTER_MS=3000 cargo run -p ghostty-apprt-gtk --example demo
This commit is contained in:
1 parent
6675db1eeb
commit
69d823cbc7
80 files changed
+17271
-4
No files matched your search
@@ -0,0 +1,196 @@
|
||||
//! Port of apprt/gtk/class/resize_overlay.zig.
|
||||
//!
|
||||
//! The overlay that shows the current size while a surface is resizing.
|
||||
//! This can be used generically to show pretty much anything with a
|
||||
//! disappearing overlay.
|
||||
//!
|
||||
//! General usage:
|
||||
//!
|
||||
//! 1. Add it to an overlay
|
||||
//! 2. Set the label with `set_label`
|
||||
//! 3. Schedule to show it with `schedule`
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ResizeOverlay(ObjectSubclass<imp::ResizeOverlay>)
|
||||
@extends adw::Bin, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
impl Default for ResizeOverlay {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResizeOverlay {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// Schedule the overlay to be shown. To avoid flickering during
|
||||
/// resizes we schedule the overlay to be shown on the next idle tick.
|
||||
pub fn schedule(&self) {
|
||||
let imp = self.imp();
|
||||
|
||||
// If we have a delay timer then we're not showing anything yet.
|
||||
if imp.delay_timer.borrow().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait until GTK is idle (we're called in the middle of resizing).
|
||||
if imp.idler.borrow().is_some() {
|
||||
return;
|
||||
}
|
||||
let weak = self.downgrade();
|
||||
let id = glib::idle_add_local_once(move || {
|
||||
if let Some(this) = weak.upgrade() {
|
||||
this.on_idle();
|
||||
}
|
||||
});
|
||||
imp.idler.replace(Some(id));
|
||||
}
|
||||
|
||||
fn on_idle(&self) {
|
||||
let imp = self.imp();
|
||||
// The idler is complete no matter what.
|
||||
imp.idler.replace(None);
|
||||
|
||||
// Cancel our previous show timer no matter what.
|
||||
if let Some(t) = imp.timer.take() {
|
||||
t.remove();
|
||||
}
|
||||
|
||||
// If we have a label to show, show ourselves; otherwise hide.
|
||||
let Some(text) = self.label() else {
|
||||
self.set_visible(false);
|
||||
return;
|
||||
};
|
||||
|
||||
imp.label.set_label(&text);
|
||||
self.set_visible(true);
|
||||
|
||||
// Setup the new timer to hide ourselves after the delay.
|
||||
let weak = self.downgrade();
|
||||
let id = glib::timeout_add_local_once(
|
||||
std::time::Duration::from_millis(u64::from(self.duration())),
|
||||
move || {
|
||||
if let Some(this) = weak.upgrade() {
|
||||
this.imp().timer.replace(None);
|
||||
this.set_visible(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
imp.timer.replace(Some(id));
|
||||
}
|
||||
}
|
||||
|
||||
mod imp {
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use super::*;
|
||||
|
||||
#[derive(gtk::CompositeTemplate, glib::Properties)]
|
||||
#[properties(wrapper_type = super::ResizeOverlay)]
|
||||
#[template(resource = "/com/mitchellh/ghostty/ui/1.2/resize-overlay.ui")]
|
||||
pub struct ResizeOverlay {
|
||||
/// The label with the text
|
||||
#[template_child]
|
||||
pub label: TemplateChild<gtk::Label>,
|
||||
|
||||
/// The time that the overlay appears (ms).
|
||||
#[property(get, set, minimum = 250, default = 750)]
|
||||
pub duration: Cell<u32>,
|
||||
|
||||
/// The first delay before any overlay is shown (ms).
|
||||
#[property(get, set, minimum = 250, default = 250, name = "first-delay")]
|
||||
pub first_delay: Cell<u32>,
|
||||
|
||||
/// The text to set on the label when scheduled.
|
||||
#[property(get, set, name = "label", nullable)]
|
||||
pub label_text: RefCell<Option<String>>,
|
||||
|
||||
/// The alignment of the label
|
||||
#[property(get, set, name = "overlay-halign", builder(gtk::Align::Center))]
|
||||
pub halign: Cell<gtk::Align>,
|
||||
#[property(get, set, name = "overlay-valign", builder(gtk::Align::Center))]
|
||||
pub valign: Cell<gtk::Align>,
|
||||
|
||||
pub idler: RefCell<Option<glib::SourceId>>,
|
||||
pub timer: RefCell<Option<glib::SourceId>>,
|
||||
pub delay_timer: RefCell<Option<glib::SourceId>>,
|
||||
}
|
||||
|
||||
impl Default for ResizeOverlay {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: TemplateChild::default(),
|
||||
duration: Cell::new(750),
|
||||
first_delay: Cell::new(250),
|
||||
label_text: RefCell::new(None),
|
||||
halign: Cell::new(gtk::Align::Center),
|
||||
valign: Cell::new(gtk::Align::Center),
|
||||
idler: RefCell::new(None),
|
||||
timer: RefCell::new(None),
|
||||
delay_timer: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ResizeOverlay {
|
||||
const NAME: &'static str = "GhosttyResizeOverlay";
|
||||
type Type = super::ResizeOverlay;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for ResizeOverlay {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
// The template sets `first-delay`; start the initial delay timer.
|
||||
let delay = self.first_delay.get();
|
||||
if delay > 0 {
|
||||
let weak = self.obj().downgrade();
|
||||
let id = glib::timeout_add_local_once(
|
||||
std::time::Duration::from_millis(u64::from(delay)),
|
||||
move || {
|
||||
if let Some(this) = weak.upgrade() {
|
||||
this.imp().delay_timer.replace(None);
|
||||
}
|
||||
},
|
||||
);
|
||||
self.delay_timer.replace(Some(id));
|
||||
}
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
if let Some(v) = self.idler.take() {
|
||||
v.remove();
|
||||
}
|
||||
if let Some(v) = self.timer.take() {
|
||||
v.remove();
|
||||
}
|
||||
if let Some(v) = self.delay_timer.take() {
|
||||
v.remove();
|
||||
}
|
||||
self.dispose_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ResizeOverlay {}
|
||||
impl BinImpl for ResizeOverlay {}
|
||||
}
|
||||
Reference in new issue
Block a user