Skip to content

Commit

Permalink
examples: Add new widget that squeezes his child
Browse files Browse the repository at this point in the history
  • Loading branch information
zophiana committed Mar 18, 2022
1 parent 8f8a9e0 commit dd83586
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 0 deletions.
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ path = "search_bar/main.rs"
name = "squares"
path = "squares/main.rs"

[[bin]]
name = "squeezer_bin"
path = "squeezer_bin/main.rs"

[[bin]]
name = "text_viewer"
path = "text_viewer/main.rs"
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ cargo run --bin basics
- [Scale Bin](./scale_bin/)
- [Search Bar](./search_bar/)
- [Squares](./squares/)
- [Squeezer Bin](./squeezer_bin/)
- [TextView](./text_viewer/)
- [Video Player](./video_player/)
- [Virtual Methods](./virtual_methods/)
13 changes: 13 additions & 0 deletions examples/squeezer_bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Squeeze Widget

This example shows a basic widget that squeezes its child to fit in the parent. The widget has a property which allows the child to keep its aspect ratio.

Note that is implementation only supports one child.

#### Screenshot with `keep_aspect_ratio` set to `true`

![Screenshot](screenshot1.png)

#### Screenshot with `keep_aspect_ratio` set to `false`

![Screenshot](screenshot2.png)
32 changes: 32 additions & 0 deletions examples/squeezer_bin/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
mod squeezer_bin;

use gtk::prelude::*;
use squeezer_bin::SqueezerBin;

fn main() {
let application = gtk::Application::builder()
.application_id("com.github.gtk-rs.examples.squeezer_bin")
.build();

application.connect_activate(|app| {
let window = gtk::ApplicationWindow::new(app);
let headerbar = gtk::HeaderBar::new();
let mode_switch = gtk::Switch::new();
let switch_label = gtk::Label::new(Some("keep aspect ratio"));
let squeezer = SqueezerBin::new();
squeezer.set_child(Some(&gtk::Label::new(Some("Hello World!"))));

headerbar.pack_start(&mode_switch);
headerbar.pack_start(&switch_label);

mode_switch
.bind_property("state", &squeezer, "keep-aspect-ratio")
.build();

window.set_titlebar(Some(&headerbar));
window.set_child(Some(&squeezer));
window.show();
});

application.run();
}
Binary file added examples/squeezer_bin/screenshot1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/squeezer_bin/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
168 changes: 168 additions & 0 deletions examples/squeezer_bin/squeezer_bin/imp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use gtk::glib;
use gtk::glib::{ParamSpec, Value};
use gtk::gsk;
use gtk::prelude::*;
use gtk::subclass::prelude::*;

use once_cell::sync::Lazy;
use std::cell::{Cell, RefCell};

fn child_size(child: &impl IsA<gtk::Widget>) -> ((i32, i32), (i32, i32)) {
let (horizontal_minimal, horizontal_natural, _, _) =
child.measure(gtk::Orientation::Horizontal, -1);
let (vertical_minimal, vertical_natural, _, _) = child.measure(gtk::Orientation::Vertical, -1);

(
(horizontal_minimal, horizontal_natural),
(vertical_minimal, vertical_natural),
)
}

#[derive(Debug, Default)]
pub struct SqueezerBin {
child: RefCell<Option<gtk::Widget>>,
keep_aspect_ratio: Cell<bool>,
}

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

impl ObjectImpl for SqueezerBin {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::new(
"child",
"Child",
"The child of the widget",
gtk::Widget::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpecBoolean::new(
"keep-aspect-ratio",
"Keep aspect ratio",
"If true it keeps the aspect ratio of the widget",
false,
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
]
});

PROPERTIES.as_ref()
}

fn property(&self, obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value {
match pspec.name() {
"child" => self.child(obj).to_value(),
"keep-aspect-ratio" => self.keep_aspect_ratio(obj).to_value(),
_ => unimplemented!(),
}
}

fn set_property(&self, obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) {
match pspec.name() {
"child" => {
self.set_child(obj, value.get::<gtk::Widget>().ok().as_ref());
}
"keep-aspect-ratio" => {
self.set_keep_aspect_ratio(obj, value.get().unwrap());
}
_ => unimplemented!(),
}
}

fn constructed(&self, obj: &Self::Type) {
obj.set_halign(gtk::Align::Fill);
obj.set_valign(gtk::Align::Fill);
obj.set_hexpand(true);
obj.set_vexpand(true);

self.parent_constructed(obj);
}

fn dispose(&self, obj: &Self::Type) {
if let Some(child) = obj.child() {
child.unparent();
}
}
}

impl WidgetImpl for SqueezerBin {
fn size_allocate(&self, widget: &Self::Type, width: i32, height: i32, baseline: i32) {
if let Some(child) = widget.child() {
let ((_, horizontal_size), (_, vertical_size)) = child_size(widget);

let (mut horizontal_zoom, mut vertical_zoom) = (
width as f32 / horizontal_size as f32,
height as f32 / vertical_size as f32,
);

if widget.keep_aspect_ratio() {
if horizontal_zoom < vertical_zoom {
vertical_zoom = horizontal_zoom;
} else {
horizontal_zoom = vertical_zoom;
}
}

let transform = gsk::Transform::new()
.scale(horizontal_zoom, vertical_zoom)
.unwrap();

child.allocate(
(width as f32 / horizontal_zoom) as i32,
(height as f32 / vertical_zoom) as i32,
baseline,
Some(&transform),
);
}
}
}

impl SqueezerBin {
pub(super) fn child(&self, _obj: &super::SqueezerBin) -> Option<gtk::Widget> {
self.child.borrow().clone()
}

pub(super) fn set_child(
&self,
obj: &super::SqueezerBin,
widget: Option<&impl IsA<gtk::Widget>>,
) {
let widget = widget.map(|w| w.as_ref());
if widget == self.child.borrow().as_ref() {
return;
}

if let Some(child) = self.child.borrow_mut().take() {
child.unparent();
}

if let Some(w) = widget {
self.child.replace(Some(w.clone()));
w.set_parent(obj);
}

obj.queue_resize();
obj.notify("child")
}

pub(super) fn keep_aspect_ratio(&self, _obj: &super::SqueezerBin) -> bool {
self.keep_aspect_ratio.get()
}

pub(super) fn set_keep_aspect_ratio(&self, obj: &super::SqueezerBin, keep_aspect_ratio: bool) {
if self.keep_aspect_ratio.get() == keep_aspect_ratio {
return;
}

self.keep_aspect_ratio.set(keep_aspect_ratio);

obj.queue_resize();
obj.notify("keep-aspect-ratio")
}
}
32 changes: 32 additions & 0 deletions examples/squeezer_bin/squeezer_bin/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
mod imp;

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

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

impl SqueezerBin {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
glib::Object::new(&[]).unwrap()
}

pub fn keep_aspect_ratio(&self) -> bool {
self.imp().keep_aspect_ratio(self)
}

pub fn set_keep_aspect_ratio(&self, keep_aspect_ratio: bool) {
self.imp().set_keep_aspect_ratio(self, keep_aspect_ratio)
}

pub fn child(&self) -> Option<gtk::Widget> {
self.imp().child(self)
}

pub fn set_child(&self, widget: Option<&impl IsA<gtk::Widget>>) {
self.imp().set_child(self, widget);
}
}

0 comments on commit dd83586

Please sign in to comment.