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)]
pub enum Message {
ItemSelect(table::Entity),
CategorySelect(Category, bool),
CategorySelect(Category),
PrintMsg(String),
NoOp,
}
@ -187,9 +188,16 @@ impl cosmic::Application for App {
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::ItemSelect(entity) => self.table_model.activate(entity),
Message::CategorySelect(category, descending) => {
self.table_model.sort(category, descending)
Message::CategorySelect(category) => {
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 => {}
}
Task::none()
@ -200,7 +208,7 @@ impl cosmic::Application for App {
cosmic::widget::responsive(|size| {
if size.width < 600.0 {
widget::compact_table(&self.table_model)
.on_item_select(Message::ItemSelect)
.on_item_left_click(Message::ItemSelect)
.item_context(|item| {
Some(widget::menu::items(
&HashMap::new(),
@ -214,8 +222,8 @@ impl cosmic::Application for App {
.apply(Element::from)
} else {
widget::table(&self.table_model)
.on_item_select(Message::ItemSelect)
.on_category_select(Message::CategorySelect)
.on_item_left_click(Message::ItemSelect)
.on_category_left_click(Message::CategorySelect)
.item_context(|item| {
Some(widget::menu::items(
&HashMap::new(),
@ -227,53 +235,21 @@ impl cosmic::Application for App {
))
})
.category_context(|category| {
Some(match category {
Category::Name => widget::menu::items(
&HashMap::new(),
vec![
widget::menu::Item::Button(
"Action on Name Category",
None,
Action::None,
),
widget::menu::Item::Button(
"Other action on Name",
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,
),
],
),
})
Some(widget::menu::items(
&HashMap::new(),
vec![
widget::menu::Item::Button(
format!("Action on {} category", category.to_string()),
None,
Action::None,
),
widget::menu::Item::Button(
format!("Other action on {} category", category.to_string()),
None,
Action::None,
),
],
))
})
.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
pub fn sort(&mut self, category: Category, ascending: bool) {
self.sort = Some((category, ascending));

View file

@ -35,13 +35,17 @@ where
#[setters(into)]
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)]
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>
@ -132,11 +136,36 @@ where
}
}))
.apply(widget::mouse_area)
.apply(|ma| {
if let Some(on_item_select) = &val.on_item_select {
ma.on_press((on_item_select)(entity))
// Left click
.apply(|mouse_area| {
if let Some(ref on_item_mb) = val.on_item_mb_left {
mouse_area.on_press((on_item_mb)(entity))
} 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)),
@ -177,25 +206,43 @@ where
item_spacing: 0,
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),
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
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
}
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
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
}

View file

@ -10,7 +10,7 @@ use crate::{
widget::{self, container, divider, menu},
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
@ -28,28 +28,45 @@ where
#[setters(into)]
pub(super) element_padding: Padding,
#[setters(into)]
pub(super) width: Length,
#[setters(into)]
pub(super) height: Length,
#[setters(into)]
pub(super) item_padding: Padding,
pub(super) item_spacing: u16,
pub(super) icon_spacing: u16,
pub(super) icon_size: u16,
#[setters(into)]
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)]
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>,
#[setters(skip)]
pub(super) category_contexts: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>>>,
// Item DND
// === Category Interaction ===
#[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)]
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)]
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)]
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>
@ -62,15 +79,14 @@ where
Message: Clone + 'static,
{
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
.model
.categories
.iter()
.cloned()
.map(|category| {
let cat_context_tree = (val.category_contexts)(category);
let cat_context_tree = (val.category_context_builder)(category);
let mut sort_state = 0;
@ -86,7 +102,7 @@ where
// Build the category header
widget::row()
.spacing(space_xxxs)
.spacing(val.icon_spacing)
.push(widget::text::heading(category.to_string()))
.push_maybe(match sort_state {
1 => Some(widget::icon::from_name("pan-up-symbolic").icon()),
@ -102,19 +118,8 @@ where
.width(category.width())
.apply(widget::mouse_area)
.apply(|mouse_area| {
if let Some(ref on_category_select) = val.on_category_select {
mouse_area.on_press((on_category_select)(
category,
if let Some(sort) = val.model.sort {
if sort.0 == category {
!sort.1
} else {
false
}
} else {
false
},
))
if let Some(ref on_category_select) = val.on_category_mb_left {
mouse_area.on_press((on_category_select)(category))
} else {
mouse_area
}
@ -149,7 +154,7 @@ where
.iter()
.map(|category| {
widget::row()
.spacing(space_xxxs)
.spacing(val.icon_spacing)
.push_maybe(
item.get_icon(*category)
.map(|icon| icon.size(val.icon_size)),
@ -192,9 +197,34 @@ where
}
}))
.apply(widget::mouse_area)
// Left click
.apply(|mouse_area| {
if let Some(ref on_item_select) = val.on_item_select {
mouse_area.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 {
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
}
@ -211,6 +241,8 @@ where
.flatten()
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children)
.width(val.width)
.height(val.height)
.spacing(val.item_spacing)
.padding(val.element_padding)
.apply(Element::from)
@ -235,37 +267,61 @@ where
Self {
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_spacing: 0,
icon_spacing: space_xxxs,
icon_size: 24,
on_item_select: None,
on_item_context: None,
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
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),
on_category_select: None,
on_category_context: None,
category_contexts: Box::new(|_| None),
on_category_mb_left: None,
on_category_mb_double: 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
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
}
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
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
}
@ -278,28 +334,42 @@ where
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
where
F: Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>> + 'static,
Message: 'static,
{
self.category_contexts = 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.category_context_builder = Box::new(context_menu_builder);
self
}
}