Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

textview: Allow zooming the text #21

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions aardvark-app/src/aardvark.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<gresources>
<gresource prefix="/org/p2panda/aardvark">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">components/zoom_level_selector.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" alias="style.css">style.css</file>
</gresource>
</gresources>

3 changes: 3 additions & 0 deletions aardvark-app/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod zoom_level_selector;

pub use self::zoom_level_selector::ZoomLevelSelector;
81 changes: 81 additions & 0 deletions aardvark-app/src/components/zoom_level_selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* zoom_level_selector.rs
*
* Copyright 2025 Julian Sparber <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
use std::cell::Cell;

use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::glib;
use sourceview::*;

mod imp {
use super::*;

#[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::ZoomLevelSelector)]
#[template(resource = "/org/p2panda/aardvark/components/zoom_level_selector.ui")]
pub struct ZoomLevelSelector {
#[template_child]
pub button: TemplateChild<gtk::Button>,
#[property(get, set)]
zoom_level: Cell<f64>,
}

#[glib::object_subclass]
impl ObjectSubclass for ZoomLevelSelector {
const NAME: &'static str = "ZoomLevelSelector";
type Type = super::ZoomLevelSelector;
type ParentType = gtk::Box;

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 ZoomLevelSelector {
fn constructed(&self) {
self.parent_constructed();
self.obj()
.bind_property("zoom_level", &*self.button, "label")
.sync_create()
.transform_to(|_, zoom_level: f64| Some(format!("{:.0}%", zoom_level * 100.0)))
.build();
}
}

impl WidgetImpl for ZoomLevelSelector {}
impl BoxImpl for ZoomLevelSelector {}
}

glib::wrapper! {
pub struct ZoomLevelSelector(ObjectSubclass<imp::ZoomLevelSelector>)
@extends gtk::Widget, gtk::Box;
}

impl ZoomLevelSelector {
pub fn new() -> Self {
glib::Object::builder()
.build()
}
}
57 changes: 57 additions & 0 deletions aardvark-app/src/components/zoom_level_selector.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="ZoomLevelSelector" parent="GtkBox">
<property name="hexpand">True</property>
<child>
<object class="GtkBox">
<property name="spacing">12</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<child>
<object class="GtkButton">
<property name="icon-name">zoom-out-symbolic</property>
<!-- Translators: Button tooltip-->
<property name="tooltip-text" translatable="yes">Zoom Out</property>
<property name="action-name">window.zoom-out</property>
<accessibility>
<!-- Translators: Button -->
<property name="label" translatable="yes">Zoom Out</property>
</accessibility>
<style>
<class name="circular"/>
<class name="flat"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="button">
<property name="hexpand">True</property>
<property name="action-name">window.zoom-one</property>
<style>
<class name="circular"/>
<class name="flat"/>
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="icon-name">zoom-in-symbolic</property>
<!-- Translators: Button tooltip-->
<property name="tooltip-text" translatable="yes">Zoom In</property>
<property name="action-name">window.zoom-in</property>
<accessibility>
<!-- Translators: Button -->
<property name="label" translatable="yes">Zoom In</property>
</accessibility>
<style>
<class name="circular"/>
<class name="flat"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

1 change: 1 addition & 0 deletions aardvark-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

mod application;
mod config;
mod components;
mod document;
mod textbuffer;
mod window;
Expand Down
4 changes: 0 additions & 4 deletions aardvark-app/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@
.user-counter {
font-weight: bold;
}

.editor {
font-size: 24px;
}
109 changes: 106 additions & 3 deletions aardvark-app/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

use std::cell;

use adw::prelude::AdwDialogExt;
use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::{gio, glib};
use gtk::{gio, glib, gdk};
use sourceview::*;

use crate::AardvarkTextBuffer;
use crate::{AardvarkTextBuffer, components::ZoomLevelSelector};

const BASE_TEXT_FONT_SIZE: f64 = 24.0;

mod imp {
use super::*;

#[derive(Debug, Default, gtk::CompositeTemplate)]
#[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::AardvarkWindow)]
#[template(resource = "/org/p2panda/aardvark/window.ui")]
pub struct AardvarkWindow {
// Template widgets
Expand All @@ -41,6 +46,12 @@ mod imp {
pub open_document_dialog: TemplateChild<adw::Dialog>,
#[template_child]
pub toast_overlay: TemplateChild<adw::ToastOverlay>,
pub css_provider: gtk::CssProvider,
pub font_size: cell::Cell<f64>,
#[property(get, set = Self::set_font_scale, default = 0.0)]
pub font_scale: cell::Cell<f64>,
#[property(get, default = 1.0)]
pub zoom_level: cell::Cell<f64>,
}

#[glib::object_subclass]
Expand All @@ -50,21 +61,99 @@ mod imp {
type ParentType = adw::ApplicationWindow;

fn class_init(klass: &mut Self::Class) {
ZoomLevelSelector::static_type();

klass.bind_template();

klass.install_action("window.zoom-in", None, |window, _, _| {
window.set_font_scale(window.font_scale() + 1.0);
});
klass.install_action("window.zoom-out", None, |window, _, _| {
window.set_font_scale(window.font_scale() - 1.0);
});
klass.install_action("window.zoom-one", None, |window, _, _| {
window.set_font_scale(0.0);
});

klass.add_binding_action(gdk::Key::plus,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-in");
klass.add_binding_action(gdk::Key::KP_Add,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-in");
klass.add_binding_action(gdk::Key::minus,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-out");
// gnome-text-editor uses this as well: probably to make it
// nicer for the US keyboard layout
klass.add_binding_action(gdk::Key::equal,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-out");
klass.add_binding_action(gdk::Key::KP_Subtract,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-out");
klass.add_binding_action(gdk::Key::_0,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-one");
klass.add_binding_action(gdk::Key::KP_0,
gdk::ModifierType::CONTROL_MASK,
"window.zoom-one");
}

fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}

#[glib::derived_properties]
impl ObjectImpl for AardvarkWindow {
fn constructed(&self) {
self.parent_constructed();

let buffer = AardvarkTextBuffer::new();
self.text_view.set_buffer(Some(&buffer));

self.font_size.set(BASE_TEXT_FONT_SIZE);
self.obj().set_font_scale(0.0);
gtk::style_context_add_provider_for_display (
&self.obj().display(),
&self.css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);

let scroll_controller = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::VERTICAL);
scroll_controller.set_propagation_phase(gtk::PropagationPhase::Capture);
let window = self.obj().clone();
scroll_controller.connect_scroll(move |scroll, _dx, dy| {
if scroll.current_event_state().contains(gdk::ModifierType::CONTROL_MASK) {
if dy < 0.0 {
window.set_font_scale(window.font_scale() + 1.0);
} else {
window.set_font_scale(window.font_scale() - 1.0);
}
glib::Propagation::Stop
} else {
glib::Propagation::Proceed
}
});
self.obj().add_controller(scroll_controller);

let zoom_gesture = gtk::GestureZoom::new();
let window = self.obj().clone();
let prev_delta = std::cell::Cell::new(0.0);
zoom_gesture.connect_scale_changed(move |_, delta| {
if prev_delta.get() == delta {
return;
}

if prev_delta.get() < delta {
window.set_font_scale(window.font_scale() + delta);
} else {
window.set_font_scale(window.font_scale() - delta);
}
prev_delta.set(delta);
});
self.obj().add_controller(zoom_gesture);

let window = self.obj().clone();
let dialog = self.open_document_dialog.clone();
self.open_document_button.connect_clicked(move |_| {
Expand All @@ -73,6 +162,20 @@ mod imp {
}
}

impl AardvarkWindow {
fn set_font_scale(&self, value: f64) {
let font_size = self.font_size.get();

self.font_scale.set(value);

let size = (font_size + self.obj().font_scale()).max(1.0);
self.zoom_level.set(size / font_size);
self.obj().notify_zoom_level();
self.css_provider.load_from_string(&format!( ".sourceview {{ font-size: {size}px; }}"));
self.obj().action_set_enabled("window.zoom-out", size > 1.0);
}
}

impl WidgetImpl for AardvarkWindow {}
impl WindowImpl for AardvarkWindow {}
impl ApplicationWindowImpl for AardvarkWindow {}
Expand Down
Loading
Loading