feat: added context menu on homepage clusters (#743)
This commit is contained in:
parent
f55a86a024
commit
2a36bb8a88
2 changed files with 157 additions and 14 deletions
|
|
@ -18,9 +18,23 @@ enum Entry {
|
|||
Separator,
|
||||
}
|
||||
|
||||
fn separator(item_width: Option<f32>) -> Rect {
|
||||
let mut sep = rect()
|
||||
.height(Size::px(1.))
|
||||
.margin(Gaps::new_symmetric(4., 0.))
|
||||
.background(MENU_BORDER);
|
||||
if let Some(w) = item_width {
|
||||
sep = sep.width(Size::px(w));
|
||||
}
|
||||
sep
|
||||
}
|
||||
|
||||
pub struct ContextMenu {
|
||||
x: f32,
|
||||
y: f32,
|
||||
upwards: bool,
|
||||
backdrop: bool,
|
||||
title: Option<String>,
|
||||
entries: Vec<Entry>,
|
||||
on_close: EventHandler<()>,
|
||||
}
|
||||
|
|
@ -30,11 +44,30 @@ impl ContextMenu {
|
|||
Self {
|
||||
x,
|
||||
y,
|
||||
upwards: false,
|
||||
backdrop: false,
|
||||
title: None,
|
||||
entries: Vec::new(),
|
||||
on_close: (|()| {}).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(mut self, title: impl Into<String>) -> Self {
|
||||
self.title = Some(title.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn open_upwards(mut self) -> Self {
|
||||
self.upwards = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn backdrop(mut self, backdrop: bool) -> Self {
|
||||
self.backdrop = backdrop;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_close(mut self, on_close: impl Into<EventHandler<()>>) -> Self {
|
||||
self.on_close = on_close.into();
|
||||
self
|
||||
|
|
@ -78,7 +111,6 @@ impl ContextMenu {
|
|||
|
||||
impl PartialEq for ContextMenu {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
// Always unequal the menu is rebuilt from scratch whenever it is reopened
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +120,8 @@ impl Component for ContextMenu {
|
|||
let on_close = self.on_close.clone();
|
||||
|
||||
let mut width = use_state(|| 0f32);
|
||||
let mut height = use_state(|| 0f32);
|
||||
let mut panel_width = use_state(|| 0f32);
|
||||
|
||||
let item_width = {
|
||||
let w = *width.read();
|
||||
|
|
@ -95,18 +129,26 @@ impl Component for ContextMenu {
|
|||
};
|
||||
|
||||
let mut list = rect().vertical().spacing(4.);
|
||||
|
||||
if let Some(title) = &self.title {
|
||||
list = list
|
||||
.child(
|
||||
// Asymmetric on purpose
|
||||
rect().padding(Gaps::new(6., 8., 4., 8.)).child(
|
||||
label()
|
||||
.text(title.clone())
|
||||
.font_size(11.)
|
||||
.font_weight(FontWeight::SEMI_BOLD)
|
||||
.max_lines(1)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(separator(item_width));
|
||||
}
|
||||
|
||||
for entry in &self.entries {
|
||||
list = match entry {
|
||||
Entry::Separator => {
|
||||
let mut sep = rect()
|
||||
.height(Size::px(1.))
|
||||
.margin(Gaps::new_symmetric(4., 0.))
|
||||
.background(MENU_BORDER);
|
||||
if let Some(w) = item_width {
|
||||
sep = sep.width(Size::px(w));
|
||||
}
|
||||
list.child(sep)
|
||||
}
|
||||
Entry::Separator => list.child(separator(item_width)),
|
||||
Entry::Action {
|
||||
icon,
|
||||
label,
|
||||
|
|
@ -133,6 +175,21 @@ impl Component for ContextMenu {
|
|||
}
|
||||
});
|
||||
|
||||
let platform = Platform::get();
|
||||
// `root_size` is physical while global positions and measured areas are logical
|
||||
let scale = (*platform.scale_factor.read() as f32).max(f32::EPSILON);
|
||||
let root = *platform.root_size.read();
|
||||
let window_width = root.width / scale;
|
||||
let window_height = root.height / scale;
|
||||
|
||||
let measured = *height.read();
|
||||
let menu_width = *panel_width.read();
|
||||
let flips = self.upwards && measured > 0. && self.y - measured >= 0.;
|
||||
let top = if flips { self.y - measured } else { self.y };
|
||||
let top = top.min(window_height - measured).max(0.);
|
||||
let left = self.x.min(window_width - menu_width).max(0.);
|
||||
let placed = measured > 0. && menu_width > 0.;
|
||||
|
||||
let panel = rect()
|
||||
.vertical()
|
||||
.padding(Gaps::new_all(6.))
|
||||
|
|
@ -144,11 +201,21 @@ impl Component for ContextMenu {
|
|||
bottom: 1.,
|
||||
left: 1.,
|
||||
}))
|
||||
.opacity(if placed { 1. } else { 0. })
|
||||
.on_sized(move |e: Event<SizedEventData>| {
|
||||
let area = e.data().area;
|
||||
if (area.height() - *height.peek()).abs() > 0.5 {
|
||||
height.set(area.height());
|
||||
}
|
||||
if (area.width() - *panel_width.peek()).abs() > 0.5 {
|
||||
panel_width.set(area.width());
|
||||
}
|
||||
})
|
||||
.child(list);
|
||||
|
||||
OverlayPopup::new()
|
||||
.backdrop(false)
|
||||
.position(Position::new_global().top(self.y).left(self.x))
|
||||
.backdrop(self.backdrop)
|
||||
.position(Position::new_global().top(top).left(left))
|
||||
.on_close(move |_| on_close.call(()))
|
||||
.child(panel.into_element())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use freya::prelude::*;
|
|||
use freya::router::RouterContext;
|
||||
use oneclient_core::clusters::Cluster;
|
||||
|
||||
use crate::components::{ART_PREVIEW_EDGE, DynamicArt, Icon, IconType};
|
||||
use crate::components::{ART_PREVIEW_EDGE, ContextMenu, DynamicArt, Icon, IconType};
|
||||
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters};
|
||||
use crate::routes::Route;
|
||||
use crate::theme;
|
||||
|
|
@ -67,7 +67,9 @@ impl Component for RecentsRow {
|
|||
index,
|
||||
items,
|
||||
progress,
|
||||
key: DiffKey::None,
|
||||
}
|
||||
.key(cluster.id)
|
||||
.into_element()
|
||||
}))
|
||||
.child(OtherVersionsTile {
|
||||
|
|
@ -94,11 +96,58 @@ fn stagger_eased(progress: f32, index: usize, items: usize) -> f32 {
|
|||
1.0 - (1.0 - local).powi(3)
|
||||
}
|
||||
|
||||
fn cluster_menu_entries(cluster_id: i64) -> [(IconType, &'static str, Route); 7] {
|
||||
[
|
||||
(
|
||||
IconType::InfoCircle,
|
||||
"Overview",
|
||||
Route::ClusterOverview { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::Terminal,
|
||||
"Logs",
|
||||
Route::ClusterLogs { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::Eye,
|
||||
"Screenshots",
|
||||
Route::ClusterScreenshots { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::CodeSnippet02,
|
||||
"Mods",
|
||||
Route::ClusterMods { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::PaintPour,
|
||||
"Shaders",
|
||||
Route::ClusterShaders { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::Colors,
|
||||
"Textures",
|
||||
Route::ClusterTextures { cluster_id },
|
||||
),
|
||||
(
|
||||
IconType::Settings01,
|
||||
"Settings",
|
||||
Route::ClusterSettings { cluster_id },
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
struct ClusterCard {
|
||||
cluster: Cluster,
|
||||
index: usize,
|
||||
items: usize,
|
||||
progress: f32,
|
||||
key: DiffKey,
|
||||
}
|
||||
|
||||
impl KeyExt for ClusterCard {
|
||||
fn write_key(&mut self) -> &mut DiffKey {
|
||||
&mut self.key
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ClusterCard {
|
||||
|
|
@ -115,6 +164,7 @@ impl Component for ClusterCard {
|
|||
let mut active_id = use_active_cluster_id();
|
||||
let active = *active_id.read() == Some(self.cluster.id);
|
||||
let mut hovering = use_state(|| false);
|
||||
let mut menu = use_state(|| None::<(f32, f32)>);
|
||||
|
||||
let a11y_id = use_a11y();
|
||||
let focus = use_focus(a11y_id);
|
||||
|
|
@ -130,6 +180,19 @@ impl Component for ClusterCard {
|
|||
*active_id.write() = Some(cluster_id);
|
||||
};
|
||||
|
||||
let menu_overlay = (*menu.read()).map(|(x, y)| {
|
||||
let mut context = ContextMenu::new(x, y)
|
||||
.open_upwards()
|
||||
.title(title.clone())
|
||||
.on_close(move |_| menu.set(None));
|
||||
for (icon, label, route) in cluster_menu_entries(cluster_id) {
|
||||
context = context.action(icon, label, move |()| {
|
||||
let _ = RouterContext::get().push(route.clone());
|
||||
});
|
||||
}
|
||||
context.into_element()
|
||||
});
|
||||
|
||||
rect()
|
||||
.key(self.cluster.id)
|
||||
.width(Size::flex(1.0))
|
||||
|
|
@ -174,6 +237,14 @@ impl Component for ClusterCard {
|
|||
.color(Color::from_af32rgb(0.3, 0, 0, 0)),
|
||||
)
|
||||
.on_press(on_press)
|
||||
.on_secondary_down(move |e: Event<PressEventData>| {
|
||||
if let PressEventData::Mouse(m) = e.data() {
|
||||
menu.set(Some((
|
||||
m.global_location.x as f32,
|
||||
m.global_location.y as f32,
|
||||
)));
|
||||
}
|
||||
})
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
|
|
@ -223,6 +294,11 @@ impl Component for ClusterCard {
|
|||
),
|
||||
),
|
||||
)
|
||||
.maybe_child(menu_overlay)
|
||||
}
|
||||
|
||||
fn render_key(&self) -> DiffKey {
|
||||
self.key.clone().or(self.default_key())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue