Added more mouse interaction options

This commit is contained in:
Adam Cosner 2025-01-11 15:46:05 -08:00
parent d4bce76213
commit 160070c1b5
4 changed files with 225 additions and 127 deletions

View file

@ -101,7 +101,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
ItemSelect(table::Entity), ItemSelect(table::Entity),
CategorySelect(Category, bool), CategorySelect(Category),
PrintMsg(String),
NoOp, NoOp,
} }
@ -187,9 +188,16 @@ impl cosmic::Application for App {
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::ItemSelect(entity) => self.table_model.activate(entity), Message::ItemSelect(entity) => self.table_model.activate(entity),
Message::CategorySelect(category, descending) => { Message::CategorySelect(category) => {
self.table_model.sort(category, descending) let mut ascending = true;
if let Some(old_sort) = self.table_model.get_sort() {
if old_sort.0 == category {
ascending = !old_sort.1;
}
}
self.table_model.sort(category, ascending)
} }
Message::PrintMsg(string) => tracing_log::log::info!("{}", string),
Message::NoOp => {} Message::NoOp => {}
} }
Task::none() Task::none()
@ -200,7 +208,7 @@ impl cosmic::Application for App {
cosmic::widget::responsive(|size| { cosmic::widget::responsive(|size| {
if size.width < 600.0 { if size.width < 600.0 {
widget::compact_table(&self.table_model) widget::compact_table(&self.table_model)
.on_item_select(Message::ItemSelect) .on_item_left_click(Message::ItemSelect)
.item_context(|item| { .item_context(|item| {
Some(widget::menu::items( Some(widget::menu::items(
&HashMap::new(), &HashMap::new(),
@ -214,8 +222,8 @@ impl cosmic::Application for App {
.apply(Element::from) .apply(Element::from)
} else { } else {
widget::table(&self.table_model) widget::table(&self.table_model)
.on_item_select(Message::ItemSelect) .on_item_left_click(Message::ItemSelect)
.on_category_select(Message::CategorySelect) .on_category_left_click(Message::CategorySelect)
.item_context(|item| { .item_context(|item| {
Some(widget::menu::items( Some(widget::menu::items(
&HashMap::new(), &HashMap::new(),
@ -227,53 +235,21 @@ impl cosmic::Application for App {
)) ))
}) })
.category_context(|category| { .category_context(|category| {
Some(match category { Some(widget::menu::items(
Category::Name => widget::menu::items( &HashMap::new(),
&HashMap::new(), vec![
vec![ widget::menu::Item::Button(
widget::menu::Item::Button( format!("Action on {} category", category.to_string()),
"Action on Name Category", None,
None, Action::None,
Action::None, ),
), widget::menu::Item::Button(
widget::menu::Item::Button( format!("Other action on {} category", category.to_string()),
"Other action on Name", None,
None, Action::None,
Action::None, ),
), ],
], ))
),
Category::Date => widget::menu::items(
&HashMap::new(),
vec![
widget::menu::Item::Button(
"Action on Date Category",
None,
Action::None,
),
widget::menu::Item::Button(
"Other action on Date",
None,
Action::None,
),
],
),
Category::Size => widget::menu::items(
&HashMap::new(),
vec![
widget::menu::Item::Button(
"Action on Size Category",
None,
Action::None,
),
widget::menu::Item::Button(
"Other action on Size",
None,
Action::None,
),
],
),
})
}) })
.apply(Element::from) .apply(Element::from)
} }

View file

@ -340,6 +340,11 @@ where
} }
} }
/// Get the sort data
pub fn get_sort(&self) -> Option<(Category, bool)> {
self.sort
}
/// Sorts items in the model, this should be called before it is drawn after all items have been added for the view /// Sorts items in the model, this should be called before it is drawn after all items have been added for the view
pub fn sort(&mut self, category: Category, ascending: bool) { pub fn sort(&mut self, category: Category, ascending: bool) {
self.sort = Some((category, ascending)); self.sort = Some((category, ascending));

View file

@ -35,13 +35,17 @@ where
#[setters(into)] #[setters(into)]
pub(super) divider_padding: Padding, pub(super) divider_padding: Padding,
// === Item Interaction ===
#[setters(skip)]
pub(super) on_item_mb_left: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_double: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_mid: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_right: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)] #[setters(skip)]
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>, pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>,
#[setters(skip)]
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
#[setters(skip)]
pub(super) on_item_context: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
} }
impl<'a, SelectionMode, Item, Category, Message> impl<'a, SelectionMode, Item, Category, Message>
@ -132,11 +136,36 @@ where
} }
})) }))
.apply(widget::mouse_area) .apply(widget::mouse_area)
.apply(|ma| { // Left click
if let Some(on_item_select) = &val.on_item_select { .apply(|mouse_area| {
ma.on_press((on_item_select)(entity)) if let Some(ref on_item_mb) = val.on_item_mb_left {
mouse_area.on_press((on_item_mb)(entity))
} else { } else {
ma mouse_area
}
})
// Double click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_left {
mouse_area.on_double_click((on_item_mb)(entity))
} else {
mouse_area
}
})
// Middle click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_mid {
mouse_area.on_middle_press((on_item_mb)(entity))
} else {
mouse_area
}
})
// Right click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_right {
mouse_area.on_right_press((on_item_mb)(entity))
} else {
mouse_area
} }
}) })
.apply(|ma| widget::context_menu(ma, context_menu)), .apply(|ma| widget::context_menu(ma, context_menu)),
@ -177,25 +206,43 @@ where
item_spacing: 0, item_spacing: 0,
icon_size: 48, icon_size: 48,
on_item_mb_left: None,
on_item_mb_double: None,
on_item_mb_mid: None,
on_item_mb_right: None,
item_context_builder: Box::new(|_| None), item_context_builder: Box::new(|_| None),
on_item_select: None,
on_item_context: None,
} }
} }
pub fn on_item_select<F>(mut self, on_select: F) -> Self pub fn on_item_left_click<F>(mut self, on_click: F) -> Self
where where
F: Fn(Entity) -> Message + 'a, F: Fn(Entity) -> Message + 'static,
{ {
self.on_item_select = Some(Box::new(on_select)); self.on_item_mb_left = Some(Box::new(on_click));
self self
} }
pub fn on_item_context<F>(mut self, on_select: F) -> Self pub fn on_item_double_click<F>(mut self, on_click: F) -> Self
where where
F: Fn(Entity) -> Message + 'a, F: Fn(Entity) -> Message + 'static,
{ {
self.on_item_context = Some(Box::new(on_select)); self.on_item_mb_double = Some(Box::new(on_click));
self
}
pub fn on_item_middle_click<F>(mut self, on_click: F) -> Self
where
F: Fn(Entity) -> Message + 'static,
{
self.on_item_mb_mid = Some(Box::new(on_click));
self
}
pub fn on_item_right_click<F>(mut self, on_click: F) -> Self
where
F: Fn(Entity) -> Message + 'static,
{
self.on_item_mb_right = Some(Box::new(on_click));
self self
} }

View file

@ -10,7 +10,7 @@ use crate::{
widget::{self, container, divider, menu}, widget::{self, container, divider, menu},
Apply, Element, Apply, Element,
}; };
use iced::{Alignment, Border, Padding}; use iced::{Alignment, Border, Length, Padding};
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED // THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
@ -28,28 +28,45 @@ where
#[setters(into)] #[setters(into)]
pub(super) element_padding: Padding, pub(super) element_padding: Padding,
#[setters(into)]
pub(super) width: Length,
#[setters(into)]
pub(super) height: Length,
#[setters(into)] #[setters(into)]
pub(super) item_padding: Padding, pub(super) item_padding: Padding,
pub(super) item_spacing: u16, pub(super) item_spacing: u16,
pub(super) icon_spacing: u16,
pub(super) icon_size: u16, pub(super) icon_size: u16,
#[setters(into)] #[setters(into)]
pub(super) divider_padding: Padding, pub(super) divider_padding: Padding,
// === Item Interaction ===
#[setters(skip)]
pub(super) on_item_mb_left: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_double: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_mid: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_item_mb_right: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)] #[setters(skip)]
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>, pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>,
#[setters(skip)] // Item DND
pub(super) category_contexts: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>>>,
// === Category Interaction ===
#[setters(skip)] #[setters(skip)]
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>, pub(super) on_category_mb_left: Option<Box<dyn Fn(Category) -> Message + 'static>>,
#[setters(skip)] #[setters(skip)]
pub(super) on_item_context: Option<Box<dyn Fn(Entity) -> Message + 'a>>, pub(super) on_category_mb_double: Option<Box<dyn Fn(Category) -> Message + 'static>>,
#[setters(skip)] #[setters(skip)]
pub(super) on_category_select: Option<Box<dyn Fn(Category, bool) -> Message + 'a>>, pub(super) on_category_mb_mid: Option<Box<dyn Fn(Category) -> Message + 'static>>,
#[setters(skip)] #[setters(skip)]
pub(super) on_category_context: Option<Box<dyn Fn(Category) -> Message + 'a>>, pub(super) on_category_mb_right: Option<Box<dyn Fn(Category) -> Message + 'static>>,
#[setters(skip)]
pub(super) category_context_builder:
Box<dyn Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>>>,
} }
impl<'a, SelectionMode, Item, Category, Message> impl<'a, SelectionMode, Item, Category, Message>
@ -62,15 +79,14 @@ where
Message: Clone + 'static, Message: Clone + 'static,
{ {
fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self { fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; // Header row
let header_row = val let header_row = val
.model .model
.categories .categories
.iter() .iter()
.cloned() .cloned()
.map(|category| { .map(|category| {
let cat_context_tree = (val.category_contexts)(category); let cat_context_tree = (val.category_context_builder)(category);
let mut sort_state = 0; let mut sort_state = 0;
@ -86,7 +102,7 @@ where
// Build the category header // Build the category header
widget::row() widget::row()
.spacing(space_xxxs) .spacing(val.icon_spacing)
.push(widget::text::heading(category.to_string())) .push(widget::text::heading(category.to_string()))
.push_maybe(match sort_state { .push_maybe(match sort_state {
1 => Some(widget::icon::from_name("pan-up-symbolic").icon()), 1 => Some(widget::icon::from_name("pan-up-symbolic").icon()),
@ -102,19 +118,8 @@ where
.width(category.width()) .width(category.width())
.apply(widget::mouse_area) .apply(widget::mouse_area)
.apply(|mouse_area| { .apply(|mouse_area| {
if let Some(ref on_category_select) = val.on_category_select { if let Some(ref on_category_select) = val.on_category_mb_left {
mouse_area.on_press((on_category_select)( mouse_area.on_press((on_category_select)(category))
category,
if let Some(sort) = val.model.sort {
if sort.0 == category {
!sort.1
} else {
false
}
} else {
false
},
))
} else { } else {
mouse_area mouse_area
} }
@ -149,7 +154,7 @@ where
.iter() .iter()
.map(|category| { .map(|category| {
widget::row() widget::row()
.spacing(space_xxxs) .spacing(val.icon_spacing)
.push_maybe( .push_maybe(
item.get_icon(*category) item.get_icon(*category)
.map(|icon| icon.size(val.icon_size)), .map(|icon| icon.size(val.icon_size)),
@ -192,9 +197,34 @@ where
} }
})) }))
.apply(widget::mouse_area) .apply(widget::mouse_area)
// Left click
.apply(|mouse_area| { .apply(|mouse_area| {
if let Some(ref on_item_select) = val.on_item_select { if let Some(ref on_item_mb) = val.on_item_mb_left {
mouse_area.on_press((on_item_select)(entity)) mouse_area.on_press((on_item_mb)(entity))
} else {
mouse_area
}
})
// Double click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_left {
mouse_area.on_double_click((on_item_mb)(entity))
} else {
mouse_area
}
})
// Middle click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_mid {
mouse_area.on_middle_press((on_item_mb)(entity))
} else {
mouse_area
}
})
// Right click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_right {
mouse_area.on_right_press((on_item_mb)(entity))
} else { } else {
mouse_area mouse_area
} }
@ -211,6 +241,8 @@ where
.flatten() .flatten()
.collect::<Vec<Element<'a, Message>>>() .collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children) .apply(widget::column::with_children)
.width(val.width)
.height(val.height)
.spacing(val.item_spacing) .spacing(val.item_spacing)
.padding(val.element_padding) .padding(val.element_padding)
.apply(Element::from) .apply(Element::from)
@ -235,37 +267,61 @@ where
Self { Self {
model, model,
element_padding: Padding::from(0),
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs), element_padding: Padding::from(0),
width: Length::Fill,
height: Length::Shrink,
item_padding: Padding::from(space_xxs).into(), item_padding: Padding::from(space_xxs).into(),
item_spacing: 0, item_spacing: 0,
icon_spacing: space_xxxs,
icon_size: 24, icon_size: 24,
on_item_select: None, divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
on_item_context: None,
on_item_mb_left: None,
on_item_mb_double: None,
on_item_mb_mid: None,
on_item_mb_right: None,
item_context_builder: Box::new(|_| None), item_context_builder: Box::new(|_| None),
on_category_select: None, on_category_mb_left: None,
on_category_context: None, on_category_mb_double: None,
category_contexts: Box::new(|_| None), on_category_mb_mid: None,
on_category_mb_right: None,
category_context_builder: Box::new(|_| None),
} }
} }
pub fn on_item_select<F>(mut self, on_select: F) -> Self pub fn on_item_left_click<F>(mut self, on_click: F) -> Self
where where
F: Fn(Entity) -> Message + 'a, F: Fn(Entity) -> Message + 'static,
{ {
self.on_item_select = Some(Box::new(on_select)); self.on_item_mb_left = Some(Box::new(on_click));
self self
} }
pub fn on_item_context<F>(mut self, on_select: F) -> Self pub fn on_item_double_click<F>(mut self, on_click: F) -> Self
where where
F: Fn(Entity) -> Message + 'a, F: Fn(Entity) -> Message + 'static,
{ {
self.on_item_context = Some(Box::new(on_select)); self.on_item_mb_double = Some(Box::new(on_click));
self
}
pub fn on_item_middle_click<F>(mut self, on_click: F) -> Self
where
F: Fn(Entity) -> Message + 'static,
{
self.on_item_mb_mid = Some(Box::new(on_click));
self
}
pub fn on_item_right_click<F>(mut self, on_click: F) -> Self
where
F: Fn(Entity) -> Message + 'static,
{
self.on_item_mb_right = Some(Box::new(on_click));
self self
} }
@ -278,28 +334,42 @@ where
self self
} }
pub fn on_category_left_click<F>(mut self, on_select: F) -> Self
where
F: Fn(Category) -> Message + 'static,
{
self.on_category_mb_left = Some(Box::new(on_select));
self
}
pub fn on_category_double_click<F>(mut self, on_select: F) -> Self
where
F: Fn(Category) -> Message + 'static,
{
self.on_category_mb_double = Some(Box::new(on_select));
self
}
pub fn on_category_middle_click<F>(mut self, on_select: F) -> Self
where
F: Fn(Category) -> Message + 'static,
{
self.on_category_mb_mid = Some(Box::new(on_select));
self
}
pub fn on_category_right_click<F>(mut self, on_select: F) -> Self
where
F: Fn(Category) -> Message + 'static,
{
self.on_category_mb_right = Some(Box::new(on_select));
self
}
pub fn category_context<F>(mut self, context_menu_builder: F) -> Self pub fn category_context<F>(mut self, context_menu_builder: F) -> Self
where where
F: Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>> + 'static, F: Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>> + 'static,
Message: 'static, Message: 'static,
{ {
self.category_contexts = Box::new(context_menu_builder); self.category_context_builder = Box::new(context_menu_builder);
self
}
pub fn on_category_select<F>(mut self, on_select: F) -> Self
where
F: Fn(Category, bool) -> Message + 'a,
{
self.on_category_select = Some(Box::new(on_select));
self
}
pub fn on_category_context<F>(mut self, on_select: F) -> Self
where
F: Fn(Category) -> Message + 'a,
{
self.on_category_context = Some(Box::new(on_select));
self self
} }
} }