Skip to content

Commit

Permalink
refactor: don't use expressions
Browse files Browse the repository at this point in the history
It is harder to deal with later
  • Loading branch information
SeaDve committed Feb 15, 2024
1 parent 4a966bc commit ea4cf5a
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 69 deletions.
1 change: 1 addition & 0 deletions data/resources/resources.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<file compressed="true">profiles.yml</file>
<file compressed="true">style.css</file>
<file compressed="true" preprocess="xml-stripblanks">ui/area-selector.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/item_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/preferences-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="gtk/help-overlay.ui">ui/shortcuts.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/view-port.ui</file>
Expand Down
31 changes: 31 additions & 0 deletions data/resources/ui/item_row.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="KoohaItemRow">
<property name="layout-manager">
<object class="GtkBoxLayout">
<property name="spacing">12</property>
</object>
</property>
<child>
<object class="GtkImage" id="warning_icon">
<property name="icon-name">warning-symbolic</property>
<style>
<class name="warning"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="title_label">
<property name="valign">center</property>
<property name="xalign">0.0</property>
<property name="ellipsize">end</property>
<property name="max-width-chars">20</property>
</object>
</child>
<child>
<object class="GtkImage" id="selected_icon">
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</template>
</interface>
166 changes: 166 additions & 0 deletions src/item_row.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use gtk::{glib, prelude::*, subclass::prelude::*};

mod imp {
use std::cell::{Cell, RefCell};

use super::*;

#[derive(Default, glib::Properties, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::ItemRow)]
#[template(resource = "/io/github/seadve/Kooha/ui/item_row.ui")]
pub struct ItemRow {
#[property(get, set = Self::set_title, explicit_notify)]
pub(super) title: RefCell<String>,
#[property(get, set = Self::set_warning_tooltip_text, explicit_notify)]
pub(super) warning_tooltip_text: RefCell<String>,
#[property(get, set = Self::set_shows_warning_icon, explicit_notify)]
pub(super) shows_warning_icon: Cell<bool>,
#[property(get, set = Self::set_shows_selected_icon, explicit_notify)]
pub(super) shows_selected_icon: Cell<bool>,
#[property(get, set = Self::set_is_selected, explicit_notify)]
pub(super) is_selected: Cell<bool>,

#[template_child]
pub(super) warning_icon: TemplateChild<gtk::Image>,
#[template_child]
pub(super) title_label: TemplateChild<gtk::Label>,
#[template_child]
pub(super) selected_icon: TemplateChild<gtk::Image>,
}

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

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 ItemRow {
fn constructed(&self) {
self.parent_constructed();

let obj = self.obj();

obj.update_title_label();
obj.update_warning_icon_tooltip_text();
obj.update_warning_icon_visibility();
obj.update_selected_icon_visibility();
obj.update_selected_icon_opacity();
}

fn dispose(&self) {
self.dispose_template();
}
}

impl WidgetImpl for ItemRow {}

impl ItemRow {
fn set_title(&self, title: String) {
let obj = self.obj();

if title == obj.title() {
return;
}

self.title.set(title);
obj.update_title_label();
obj.notify_title();
}

fn set_warning_tooltip_text(&self, warning_tooltip_text: String) {
let obj = self.obj();

if warning_tooltip_text == obj.warning_tooltip_text() {
return;
}

self.warning_tooltip_text.set(warning_tooltip_text);
obj.update_warning_icon_tooltip_text();
obj.notify_warning_tooltip_text();
}

fn set_shows_warning_icon(&self, shows_warning_icon: bool) {
let obj = self.obj();

if shows_warning_icon == obj.shows_warning_icon() {
return;
}

self.shows_warning_icon.set(shows_warning_icon);
obj.update_warning_icon_visibility();
obj.notify_shows_warning_icon();
}

fn set_shows_selected_icon(&self, shows_selected_icon: bool) {
let obj = self.obj();

if shows_selected_icon == obj.shows_selected_icon() {
return;
}

self.shows_selected_icon.set(shows_selected_icon);
obj.update_selected_icon_visibility();
obj.notify_shows_selected_icon();
}

fn set_is_selected(&self, is_selected: bool) {
let obj = self.obj();

if is_selected == obj.is_selected() {
return;
}

self.is_selected.set(is_selected);
obj.update_selected_icon_opacity();
obj.notify_is_selected();
}
}
}

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

impl ItemRow {
pub fn new() -> Self {
glib::Object::new()
}

fn update_title_label(&self) {
let imp = self.imp();
imp.title_label.set_label(&self.title());
}

fn update_warning_icon_tooltip_text(&self) {
let imp = self.imp();
imp.warning_icon
.set_tooltip_text(Some(&self.warning_tooltip_text()));
}

fn update_warning_icon_visibility(&self) {
let imp = self.imp();
imp.warning_icon.set_visible(self.shows_warning_icon());
}

fn update_selected_icon_visibility(&self) {
let imp = self.imp();
imp.selected_icon.set_visible(self.shows_selected_icon());
}

fn update_selected_icon_opacity(&self) {
let imp = self.imp();
let opacity = if self.is_selected() { 1.0 } else { 0.0 };
imp.selected_icon.set_opacity(opacity);
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod config;
mod format_time;
mod help;
mod i18n;
mod item_row;
mod pipeline;
mod preferences_dialog;
mod profile;
Expand Down
135 changes: 66 additions & 69 deletions src/preferences_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use gettextrs::gettext;
use gtk::{
gio,
glib::{self, clone, closure, BoxedAnyObject},
pango,
};

use crate::{profile::Profile, settings::Settings, IS_EXPERIMENTAL_MODE};
use crate::{item_row::ItemRow, profile::Profile, settings::Settings, IS_EXPERIMENTAL_MODE};

/// Used to represent "none" profile in the profiles model
type NoneProfile = BoxedAnyObject;

const PROFILE_ROW_SELECTED_ITEM_NOTIFY_HANDLER_ID_KEY: &str =
"kooha-profile-row-selected-item-notify-handler-id";

mod imp {
use std::cell::OnceCell;

Expand Down Expand Up @@ -84,9 +86,7 @@ mod imp {
let active_profile = settings.profile();

self.profile_row
.set_factory(Some(&profile_row_factory(&self.profile_row, false)));
self.profile_row
.set_list_factory(Some(&profile_row_factory(&self.profile_row, true)));
.set_factory(Some(&profile_row_factory(&self.profile_row)));
let profiles = Profile::all()
.inspect_err(|err| tracing::error!("Failed to load profiles: {:?}", err))
.unwrap_or_default();
Expand Down Expand Up @@ -214,79 +214,76 @@ impl PreferencesDialog {
}
}

fn profile_row_factory(
profile_row: &adw::ComboRow,
show_selected_indicator: bool,
) -> gtk::SignalListItemFactory {
fn profile_row_factory(profile_row: &adw::ComboRow) -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();

factory.connect_setup(clone!(@weak profile_row => move |_, list_item| {
let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
let item_expression = list_item.property_expression("item");

let hbox = gtk::Box::builder().spacing(12).build();

let warning_indicator = gtk::Image::builder()
.tooltip_text(gettext("This format is experimental and unsupported."))
.icon_name("warning-symbolic")
.build();
warning_indicator.add_css_class("warning");
hbox.append(&warning_indicator);

item_expression
.chain_closure::<bool>(closure!(
|_: Option<glib::Object>, obj: Option<glib::Object>| {
obj.as_ref()
.and_then(|obj| profile_from_obj(obj))
.is_some_and(|profile| profile.is_experimental())
}
))
.bind(&warning_indicator, "visible", glib::Object::NONE);

let label = gtk::Label::builder()
.valign(gtk::Align::Center)
.xalign(0.0)
.ellipsize(pango::EllipsizeMode::End)
.max_width_chars(20)
.build();
hbox.append(&label);

item_expression
.chain_closure::<String>(closure!(
|_: Option<glib::Object>, obj: Option<glib::Object>| {
obj.as_ref()
.and_then(|o| profile_from_obj(o))
.map_or(gettext("None"), |profile| profile.name().to_string())
}
))
.bind(&label, "label", glib::Object::NONE);

if show_selected_indicator {
let selected_indicator = gtk::Image::from_icon_name("object-select-symbolic");
hbox.append(&selected_indicator);

gtk::ClosureExpression::new::<f64>(
&[
profile_row.property_expression("selected-item"),
item_expression,
],
closure!(|_: Option<glib::Object>,
selected_item: Option<glib::Object>,
item: Option<glib::Object>| {
if item == selected_item {
1.0
} else {
0.0
}
}),
)
.bind(&selected_indicator, "opacity", glib::Object::NONE);

let item_row = ItemRow::new();
item_row.set_warning_tooltip_text(gettext("This format is experimental and unsupported."));

list_item.set_child(Some(&item_row));
}));

factory.connect_bind(clone!(@weak profile_row => move |_, list_item| {
let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();

let item_row = list_item.child().unwrap().downcast::<ItemRow>().unwrap();

let item = list_item.item().unwrap();
let profile = profile_from_obj(&item);

item_row.set_shows_warning_icon(profile.is_some_and(|profile| profile.is_experimental()));
item_row.set_title(
profile.map_or_else(|| gettext("None"), |profile| profile.name().to_string()),
);

// Only show the selected icon when it is inside the given row's popover. This assumes that
// the parent of the given row is not a popover, so we can tell which is which.
if item_row.ancestor(gtk::Popover::static_type()).is_some() {
debug_assert!(profile_row.ancestor(gtk::Popover::static_type()).is_none());

item_row.set_shows_selected_icon(true);

unsafe {
list_item.set_data(
PROFILE_ROW_SELECTED_ITEM_NOTIFY_HANDLER_ID_KEY,
profile_row.connect_selected_item_notify(
clone!(@weak list_item => move |profile_row| {
update_item_row_is_selected(profile_row, &list_item);
}),
),
);
}

update_item_row_is_selected(&profile_row, list_item);
} else {
item_row.set_shows_selected_icon(false);
}
}));

factory.connect_unbind(clone!(@weak profile_row => move |_, list_item| {
let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();

list_item.set_child(Some(&hbox));
unsafe {
if let Some(handler_id) =
list_item.steal_data(PROFILE_ROW_SELECTED_ITEM_NOTIFY_HANDLER_ID_KEY)
{
profile_row.disconnect(handler_id);
}
}
}));

factory
}

fn update_item_row_is_selected(row: &adw::ComboRow, list_item: &gtk::ListItem) {
let item_row = list_item.child().unwrap().downcast::<ItemRow>().unwrap();

item_row.set_is_selected(row.selected_item() == list_item.item());
}

/// Returns `Some` if the object is a `Profile`, otherwise `None`, if the object is a `NoneProfile`.
fn profile_from_obj(obj: &glib::Object) -> Option<&Profile> {
if let Some(profile) = obj.downcast_ref::<Profile>() {
Expand Down

0 comments on commit ea4cf5a

Please sign in to comment.