Commit 682bd3b7 authored by Bilal Elmoussaoui's avatar Bilal Elmoussaoui
Browse files

make the Drawer widget scrollable

parent 63cd85fd
Pipeline #294786 passed with stage
in 14 minutes and 51 seconds
......@@ -64,13 +64,13 @@
<property name="margin-bottom">10</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="zoom_out_btn">
<object class="GtkButton">
<property name="action-name">win.zoom_out</property>
<property name="icon-name">zoom-out-symbolic</property>
</object>
</child>
<child>
<object class="GtkButton" id="zoom_reset_btn">
<object class="GtkButton">
<property name="sensitive">False</property>
<property name="action-name">win.zoom_reset</property>
<child>
......@@ -82,7 +82,7 @@
</object>
</child>
<child>
<object class="GtkButton" id="zoom_in_btn">
<object class="GtkButton">
<property name="action-name">win.zoom_in</property>
<property name="icon-name">zoom-in-symbolic</property>
</object>
......@@ -195,7 +195,7 @@
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
......
......@@ -17,7 +17,11 @@ pub enum Filter {
mod imp {
use super::*;
use glib::{subclass::Signal, ParamSpec};
use glib::{
object::{Interface, InterfaceRef},
subclass::Signal,
ParamFlags, ParamSpec,
};
use once_cell::sync::Lazy;
use std::cell::{Cell, RefCell};
......@@ -33,7 +37,12 @@ mod imp {
pub zoom: Cell<f64>,
pub filter: Cell<Filter>,
pub snapshot: RefCell<Option<gsk::RenderNode>>,
pub vadjustment: RefCell<Option<gtk::Adjustment>>,
pub vadjustment_signal: RefCell<Option<glib::SignalHandlerId>>,
pub hadjustment: RefCell<Option<gtk::Adjustment>>,
pub hadjustment_signal: RefCell<Option<glib::SignalHandlerId>>,
pub hscroll_policy: Cell<gtk::ScrollablePolicy>,
pub vscroll_policy: Cell<gtk::ScrollablePolicy>,
}
#[glib::object_subclass]
......@@ -41,6 +50,7 @@ mod imp {
const NAME: &'static str = "Drawer";
type ParentType = gtk::Widget;
type Type = super::Drawer;
type Interfaces = (gtk::Scrollable,);
fn new() -> Self {
Self {
......@@ -52,7 +62,12 @@ mod imp {
can_undo: Cell::new(false),
zoom: Cell::new(1.0),
filter: Cell::new(Filter::Blur),
snapshot: RefCell::new(None),
hadjustment: RefCell::new(None),
hadjustment_signal: RefCell::new(None),
vadjustment: RefCell::new(None),
vadjustment_signal: RefCell::new(None),
hscroll_policy: Cell::new(gtk::ScrollablePolicy::Minimum),
vscroll_policy: Cell::new(gtk::ScrollablePolicy::Minimum),
}
}
......@@ -68,38 +83,90 @@ mod imp {
}
fn constructed(&self, obj: &Self::Type) {
obj.set_halign(gtk::Align::Center);
obj.set_valign(gtk::Align::Center);
obj.set_hexpand(true);
obj.set_vexpand(true);
obj.set_halign(gtk::Align::Fill);
obj.set_valign(gtk::Align::Fill);
let controller = gtk::GestureClick::new();
controller.connect_pressed(clone!(@weak obj => move |_, _n_press, x, y| {
let gesture_click = gtk::GestureClick::new();
gesture_click.connect_pressed(clone!(@weak obj => move |_, _n_press, x, y| {
let self_ = imp::Drawer::from_instance(&obj);
self_.start_position.replace(Some((x, y)));
}));
controller.connect_released(clone!(@weak obj => move |_, _n_press, x, y| {
gesture_click.connect_released(clone!(@weak obj => move |_, _n_press, x, y| {
let self_ = imp::Drawer::from_instance(&obj);
self_.end_position.replace(Some((x, y)));
obj.apply_filter();
}));
obj.connect_notify(Some("zoom"), move |widget, _| {
widget.queue_resize();
obj.add_controller(&gesture_click);
let gesture_zoom = gtk::GestureZoom::new();
gesture_zoom.connect_scale_changed(clone!(@weak obj => move |_, scale| {
println!("gesture zoom scale: {}", scale);
obj.set_zoom(scale);
}));
obj.add_controller(&gesture_zoom);
/*
let content_formats = gdk::ContentFormatsBuilder::new()
.add_mime_type("text/uri-list")
.add_mime_type("image/png")
.add_mime_type("image/jpeg")
.add_mime_type("image/jpg")
.add_mime_type("image/bmp")
.build();
let drop_target = gtk::DropTargetAsync::new(Some(&content_formats), gdk::DragAction::COPY | gdk::DragAction::LINK);
drop_target.connect_drop(move |_, drop, x, y| {
let formats = drop.formats().unwrap();
// we have a uri or a list of them
if formats.contain_mime_type("text/uri-list") {
drop.read_async(&["text/uri-list"], glib::PRIORITY_DEFAULT, gio::NONE_CANCELLABLE, move |r| {
if let Ok((stream, string)) = r {
println!("{}", string);
let mut buffer = Vec::new();
stream.read_all(&mut buffer, gio::NONE_CANCELLABLE).unwrap();
let ctx = String::from_utf8(buffer).unwrap();
println!("{}", ctx);
};
//drop.finish(gdk::DragAction::LINK);
});
// we have textures
} else {
drop.read_async(&["image/png", "image/jpeg", "image/jpg", "image/bmp"], glib::PRIORITY_DEFAULT, gio::NONE_CANCELLABLE, move |r| {
if let Ok((stream, string)) = r {
println!("{}", string);
/*
let mut buffer = Vec::new();
stream.read_all(&mut buffer, gio::NONE_CANCELLABLE).unwrap();
let gdk::Texture::from_f
let texture = gdk::Texture::from_file().unwrap();*/
}
});
}
true
});
obj.add_controller(&controller);
obj.add_controller(&drop_target);
*/
}
fn properties() -> &'static [ParamSpec] {
static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
let interface: InterfaceRef<gtk::Scrollable> = Interface::from_type(gtk::Scrollable::static_type()).unwrap();
vec![
ParamSpec::new_boolean("can-undo", "Can undo", "whether you can undo", false, glib::ParamFlags::READWRITE),
ParamSpec::new_boolean("can-redo", "Can redo", "whether you can redo", false, glib::ParamFlags::READWRITE),
ParamSpec::new_double("zoom", "Zoom", "Current zoom level", 0.1, 2.0, 1.0, glib::ParamFlags::READWRITE),
ParamSpec::new_enum("filter", "Filter", "The applied filter", Filter::static_type(), Filter::Blur as i32, glib::ParamFlags::READWRITE),
ParamSpec::new_boolean("can-undo", "Can undo", "whether you can undo", false, ParamFlags::READWRITE),
ParamSpec::new_boolean("can-redo", "Can redo", "whether you can redo", false, ParamFlags::READWRITE),
ParamSpec::new_double("zoom", "Zoom", "Current zoom level", 0.1, 2.0, 1.0, ParamFlags::READWRITE),
ParamSpec::new_enum("filter", "Filter", "The applied filter", Filter::static_type(), Filter::Blur as i32, ParamFlags::READWRITE),
// Scrollable properties
ParamSpec::new_override("hscroll-policy", &interface.find_property("hscroll-policy").unwrap()),
ParamSpec::new_override("vscroll-policy", &interface.find_property("vscroll-policy").unwrap()),
ParamSpec::new_override("hadjustment", &interface.find_property("hadjustment").unwrap()),
ParamSpec::new_override("vadjustment", &interface.find_property("vadjustment").unwrap()),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &ParamSpec) {
fn set_property(&self, obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &ParamSpec) {
match pspec.name() {
"can-undo" => {
let can_undo = value.get().unwrap();
......@@ -117,6 +184,22 @@ mod imp {
let filter = value.get().unwrap();
self.filter.set(filter);
}
"hadjustment" => {
let hadjustment = value.get().unwrap();
obj.set_hadjustment(hadjustment);
}
"hscroll-policy" => {
let hscroll_policy = value.get().unwrap();
self.hscroll_policy.replace(hscroll_policy);
}
"vadjustment" => {
let vadjustment = value.get().unwrap();
obj.set_vadjustment(vadjustment);
}
"vscroll-policy" => {
let vscroll_policy = value.get().unwrap();
self.vscroll_policy.replace(vscroll_policy);
}
_ => unimplemented!(),
}
}
......@@ -127,10 +210,15 @@ mod imp {
"can-redo" => self.can_redo.get().to_value(),
"zoom" => self.zoom.get().to_value(),
"filter" => self.filter.get().to_value(),
"hadjustment" => self.hadjustment.borrow().to_value(),
"vadjustment" => self.vadjustment.borrow().to_value(),
"hscroll-policy" => self.hscroll_policy.get().to_value(),
"vscroll-policy" => self.vscroll_policy.get().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for Drawer {
fn measure(&self, _widget: &Self::Type, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
let zoom = self.zoom.get();
......@@ -142,24 +230,48 @@ mod imp {
};
if orientation == gtk::Orientation::Horizontal {
(width, width, -1, -1)
(0, width, -1, -1)
} else {
(height, height, -1, -1)
(0, height, -1, -1)
}
}
fn snapshot(&self, widget: &Self::Type, snapshot: &gtk::Snapshot) {
if let Some(texture) = widget.texture() {
let hadj = widget.hadjustment().unwrap();
let vadj = widget.vadjustment().unwrap();
let zoom = self.zoom.get() as f32;
snapshot.translate(&graphene::Point::new(-hadj.value() as f32, -vadj.value() as f32));
snapshot.push_clip(&graphene::Rect::new(hadj.value() as f32, vadj.value() as f32, hadj.page_size() as f32, vadj.page_size() as f32));
snapshot.scale(zoom, zoom);
snapshot.append_texture(&texture, &graphene::Rect::new(0.0, 0.0, texture.width() as f32, texture.height() as f32));
snapshot.pop();
}
}
fn size_allocate(&self, widget: &Self::Type, width: i32, height: i32, _baseline: i32) {
if let Some(texture) = widget.texture() {
let zoom = self.zoom.get();
let hadjustment = widget.hadjustment().unwrap();
hadjustment.configure(hadjustment.value() * zoom, 0.0, texture.width() as f64 * zoom, 0.1 * width as f64, 0.9 * width as f64, width as f64);
let vadjustment = widget.vadjustment().unwrap();
vadjustment.configure(vadjustment.value() * zoom, 0.0, texture.height() as f64 * zoom, 0.1 * height as f64, 0.9 * height as f64, height as f64);
}
}
}
impl ScrollableImpl for Drawer {
fn border(&self, _scrollable: &Self::Type) -> Option<gtk::Border> {
None
}
}
}
glib::wrapper! {
pub struct Drawer(ObjectSubclass<imp::Drawer>) @extends gtk::Widget;
pub struct Drawer(ObjectSubclass<imp::Drawer>) @extends gtk::Widget, @implements gtk::Scrollable;
}
impl Drawer {
......@@ -171,6 +283,38 @@ impl Drawer {
self.property("zoom").unwrap().get().unwrap()
}
pub fn set_hadjustment(&self, adj: Option<gtk::Adjustment>) {
let self_ = imp::Drawer::from_instance(self);
if let Some(signal_id) = self_.hadjustment_signal.borrow_mut().take() {
let old_adj = self_.hadjustment.borrow().as_ref().unwrap().clone();
old_adj.disconnect(signal_id);
}
if let Some(ref adjustment) = adj {
let signal_id = adjustment.connect_value_changed(clone!(@weak self as this => move |_| {
this.queue_draw();
}));
self_.hadjustment_signal.replace(Some(signal_id));
}
self_.hadjustment.replace(adj);
}
pub fn set_vadjustment(&self, adj: Option<gtk::Adjustment>) {
let self_ = imp::Drawer::from_instance(self);
if let Some(signal_id) = self_.vadjustment_signal.borrow_mut().take() {
let old_adj = self_.vadjustment.borrow().as_ref().unwrap().clone();
old_adj.disconnect(signal_id);
}
if let Some(ref adjustment) = adj {
let signal_id = adjustment.connect_value_changed(clone!(@weak self as this => move |_| {
this.queue_draw();
}));
self_.vadjustment_signal.replace(Some(signal_id));
}
self_.vadjustment.replace(adj);
}
pub fn draw_from_file(&self, file: &gio::File) -> anyhow::Result<()> {
let texture = gdk::Texture::from_file(file)?;
self.load_texture(texture);
......@@ -191,12 +335,12 @@ impl Drawer {
self_.end_position.replace(None);
self.set_property("can-undo", &false).unwrap();
self.set_property("can-redo", &false).unwrap();
self.set_property("zoom", &1.0).unwrap();
self.queue_resize();
self.set_zoom(1.0);
}
pub fn set_zoom(&self, new_zoom: f64) {
self.set_property("zoom", &new_zoom).unwrap();
self.queue_allocate();
self.queue_draw();
}
......@@ -271,6 +415,7 @@ impl Drawer {
let (x1, y1) = self_.end_position.take().unwrap();
let width = x1 - x0;
let height = y1 - y0;
let zoom = self.zoom();
// figure out the real start position of the rectangle
let (x, y) = if height < 0.0 && width > 0.0 {
(x0, y1)
......@@ -281,7 +426,7 @@ impl Drawer {
} else {
(x0, y0)
};
let filter_bounds = graphene::Rect::new(x as f32, y as f32, width.abs() as f32, height.abs() as f32);
let filter_bounds = graphene::Rect::new((x / zoom) as f32, (y / zoom) as f32, (width.abs() / zoom) as f32, (height.abs() / zoom) as f32);
let renderer = self.root().unwrap().upcast::<gtk::Native>().renderer().unwrap();
let snapshot = gtk::Snapshot::new();
......@@ -302,9 +447,9 @@ impl Drawer {
};
let node = snapshot.free_to_node().unwrap();
println!("{:#}", node.node_type());
// drop this downcasting once https://github.com/gtk-rs/gtk4-rs/pull/500 is merged and landed
let texture = if let Some(n) = node.downcast_ref::<gsk::ClipNode>() {
let texture = if let Some(n) = node.downcast_ref::<gsk::TextureNode>() {
renderer.render_texture(n, None::<&graphene::Rect>).unwrap()
} else {
renderer.render_texture(node.downcast_ref::<gsk::ContainerNode>().unwrap(), None::<&graphene::Rect>).unwrap()
......
......@@ -120,6 +120,10 @@ impl Window {
}
}
get_action!(self, @save).set_enabled(view == View::Image);
get_action!(self, @copy).set_enabled(view == View::Image);
get_action!(self, @paste).set_enabled(view == View::Image);
get_action!(self, @undo).set_enabled(false);
get_action!(self, @redo).set_enabled(false);
get_action!(self, @zoom_in).set_enabled(view == View::Image);
get_action!(self, @zoom_out).set_enabled(view == View::Image);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment