feat: added tooltips (#837)
This commit is contained in:
parent
0a6c9e0f74
commit
d32d3e1782
16 changed files with 408 additions and 11 deletions
|
|
@ -141,6 +141,7 @@ fn cluster_settings_button(cluster_id: i64) -> impl IntoElement {
|
|||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip("Cluster settings")
|
||||
.on_press(move |_| {
|
||||
let _ = RouterContext::get().push(Route::ClusterOverview { cluster_id });
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::borrow::Cow;
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::{
|
||||
components::{Icon, IconTint, IconType},
|
||||
components::{Icon, IconTint, IconType, TooltipPlacement, tooltip::use_tooltip_anchor},
|
||||
theme::colors,
|
||||
};
|
||||
|
||||
|
|
@ -53,6 +53,8 @@ pub struct Button {
|
|||
enabled: bool,
|
||||
focusable: bool,
|
||||
alt: Option<Box<str>>,
|
||||
tooltip: Option<Box<str>>,
|
||||
tooltip_placement: TooltipPlacement,
|
||||
on_press: Option<EventHandler<Event<PressEventData>>>,
|
||||
|
||||
elements: Vec<Element>,
|
||||
|
|
@ -111,6 +113,8 @@ impl Button {
|
|||
enabled: true,
|
||||
focusable: true,
|
||||
alt: None,
|
||||
tooltip: None,
|
||||
tooltip_placement: TooltipPlacement::default(),
|
||||
on_press: None,
|
||||
elements: Vec::new(),
|
||||
key: DiffKey::None,
|
||||
|
|
@ -203,6 +207,20 @@ impl Button {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, tooltip: impl Into<Box<str>>) -> Self {
|
||||
let tooltip = tooltip.into();
|
||||
if self.alt.is_none() {
|
||||
self.alt = Some(tooltip.clone());
|
||||
}
|
||||
self.tooltip = Some(tooltip);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip_placement(mut self, placement: TooltipPlacement) -> Self {
|
||||
self.tooltip_placement = placement;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cursor_icon(mut self, cursor_icon: CursorIcon) -> Self {
|
||||
self.cursor_icon = cursor_icon;
|
||||
self
|
||||
|
|
@ -247,6 +265,8 @@ impl Component for Button {
|
|||
let a11y_id = use_a11y();
|
||||
let focus = use_focus(a11y_id);
|
||||
|
||||
let tooltip = use_tooltip_anchor(self.tooltip.clone(), self.tooltip_placement);
|
||||
|
||||
let enabled = use_reactive(&self.enabled);
|
||||
|
||||
let cursor_icon = self.cursor_icon;
|
||||
|
|
@ -335,7 +355,7 @@ impl Component for Button {
|
|||
rect = rect.cursor(CursorIcon::NotAllowed);
|
||||
}
|
||||
|
||||
rect
|
||||
tooltip.attach(rect)
|
||||
}
|
||||
|
||||
fn render_key(&self) -> DiffKey {
|
||||
|
|
@ -444,6 +464,7 @@ pub fn open_folder_button(folder: std::path::PathBuf) -> Button {
|
|||
Button::new()
|
||||
.secondary()
|
||||
.icon()
|
||||
.tooltip("Open folder")
|
||||
.on_press(move |_| {
|
||||
std::fs::create_dir_all(&folder).ok();
|
||||
crate::platform::open_path(&folder.to_string_lossy());
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ mod tab_bar;
|
|||
mod text_input;
|
||||
mod toasts;
|
||||
mod toggle;
|
||||
mod tooltip;
|
||||
mod update_prompt;
|
||||
mod version_card;
|
||||
pub mod upload_mclogs;
|
||||
|
|
@ -94,5 +95,6 @@ pub use tab_bar::{TabBar, TabItem};
|
|||
pub use text_input::{TextInput, validate_memory, validate_number};
|
||||
pub use toasts::Toasts;
|
||||
pub use toggle::{toggle, toggle_controlled};
|
||||
pub use tooltip::{TooltipHost, TooltipPlacement, use_provide_tooltips};
|
||||
pub use update_prompt::UpdatePromptOverlay;
|
||||
pub use version_card::VersionCard;
|
||||
|
|
|
|||
|
|
@ -261,17 +261,20 @@ impl Component for NavbarRight {
|
|||
.spacing(8.)
|
||||
.child(
|
||||
super::navbar_button()
|
||||
.tooltip("Notifications")
|
||||
.child(notification_bell(unread))
|
||||
.on_press(open_notifications),
|
||||
)
|
||||
.child(
|
||||
super::navbar_button()
|
||||
.tooltip("Settings")
|
||||
.child(Icon::new(IconType::Settings02).size(20.))
|
||||
.on_press(open_settings),
|
||||
)
|
||||
.child(
|
||||
super::navbar_button()
|
||||
.padding(0.0)
|
||||
.tooltip("Switch account")
|
||||
.on_press(open_account_switcher)
|
||||
.child(
|
||||
Avatar::new(account_uuid)
|
||||
|
|
|
|||
|
|
@ -44,16 +44,19 @@ pub fn window_controls() -> impl IntoElement {
|
|||
.layer(Layer::OverlayLevel(15))
|
||||
.child(
|
||||
navbar_button()
|
||||
.tooltip("Minimize")
|
||||
.child(Icon::new(IconType::Minus))
|
||||
.on_press(minimize),
|
||||
)
|
||||
.child(
|
||||
navbar_button()
|
||||
.tooltip("Maximize")
|
||||
.child(Icon::new(IconType::Maximize01).size(20.))
|
||||
.on_press(maximize),
|
||||
)
|
||||
.child(
|
||||
navbar_button()
|
||||
.tooltip("Close")
|
||||
.child(Icon::new(IconType::X))
|
||||
.on_press(close),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{Button, Icon, IconType};
|
||||
use crate::components::{Button, Icon, IconType, TooltipPlacement};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
|
|
@ -52,13 +52,18 @@ impl Component for Pagination {
|
|||
let current = *page.read();
|
||||
let last = total_pages.saturating_sub(1);
|
||||
|
||||
let icon_btn = move |target: usize, available: bool, icon: IconType| {
|
||||
let icon_btn = move |target: usize,
|
||||
available: bool,
|
||||
icon: IconType,
|
||||
tooltip: &'static str| {
|
||||
let mut page = page;
|
||||
let enabled = available && interactive;
|
||||
Button::new()
|
||||
.secondary()
|
||||
.icon()
|
||||
.enabled(enabled)
|
||||
.tooltip(tooltip)
|
||||
.tooltip_placement(TooltipPlacement::Top)
|
||||
.on_press(move |_| page.set(target))
|
||||
.child(Icon::new(icon).size(14.).color(if enabled {
|
||||
colors::fg_primary()
|
||||
|
|
@ -130,11 +135,17 @@ impl Component for Pagination {
|
|||
.main_align(Alignment::Center)
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(6.)
|
||||
.child(icon_btn(0, current > 0, IconType::ChevronsLeft))
|
||||
.child(icon_btn(
|
||||
0,
|
||||
current > 0,
|
||||
IconType::ChevronsLeft,
|
||||
"First page",
|
||||
))
|
||||
.child(icon_btn(
|
||||
current.saturating_sub(1),
|
||||
current > 0,
|
||||
IconType::ArrowLeft,
|
||||
"Previous page",
|
||||
));
|
||||
|
||||
if start > 0 {
|
||||
|
|
@ -155,7 +166,17 @@ impl Component for Pagination {
|
|||
row = row.child(num_btn(last));
|
||||
}
|
||||
|
||||
row.child(icon_btn(current + 1, current < last, IconType::ArrowRight))
|
||||
.child(icon_btn(last, current < last, IconType::ChevronsRight))
|
||||
row.child(icon_btn(
|
||||
current + 1,
|
||||
current < last,
|
||||
IconType::ArrowRight,
|
||||
"Next page",
|
||||
))
|
||||
.child(icon_btn(
|
||||
last,
|
||||
current < last,
|
||||
IconType::ChevronsRight,
|
||||
"Last page",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,6 +324,7 @@ fn header_row(
|
|||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip("Close")
|
||||
.on_press(on_close)
|
||||
.child(Icon::new(IconType::XClose).size(18.)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use oneclient_events::Level;
|
|||
|
||||
use crate::{
|
||||
ui::{divider},
|
||||
components::{Button, ButtonSize, Icon, IconType, progress_track},
|
||||
components::{Button, ButtonSize, Icon, IconType, TooltipPlacement, progress_track},
|
||||
hooks::{use_dispatch, use_notifications_snapshot},
|
||||
motion::use_animations_enabled,
|
||||
notifications::{InboxEntry, NotificationActionKind},
|
||||
|
|
@ -328,6 +328,8 @@ fn close_button(dispatch: crate::Actions, id: u64) -> impl IntoElement {
|
|||
.size(ButtonSize::Icon)
|
||||
.width(Size::px(28.))
|
||||
.height(Size::px(28.))
|
||||
.tooltip("Dismiss")
|
||||
.tooltip_placement(TooltipPlacement::Left)
|
||||
.on_press(move |_| dispatch.dismiss_toast(id))
|
||||
.child(
|
||||
Icon::new(IconType::XClose)
|
||||
|
|
|
|||
315
packages/oneclient_app/src/components/tooltip.rs
Normal file
315
packages/oneclient_app/src/components/tooltip.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use freya::animation::{AnimNum, Ease, OnCreation, use_animation};
|
||||
use freya::prelude::*;
|
||||
|
||||
use crate::components::OVERLAY_MAX_LEVEL;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::{border_all_color, clamp_to_window, window_logical_size};
|
||||
|
||||
const SHOW_DELAY: Duration = Duration::from_millis(400);
|
||||
const ANCHOR_GAP: f32 = 6.;
|
||||
const MAX_WIDTH: f32 = 260.;
|
||||
const FADE_MS: u64 = 110;
|
||||
const HOVER_SLACK: f32 = 1.;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum TooltipPlacement {
|
||||
Top,
|
||||
#[default]
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
struct AnchorRect {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
impl AnchorRect {
|
||||
fn contains(&self, x: f32, y: f32) -> bool {
|
||||
x >= self.x - HOVER_SLACK
|
||||
&& x <= self.x + self.width + HOVER_SLACK
|
||||
&& y >= self.y - HOVER_SLACK
|
||||
&& y <= self.y + self.height + HOVER_SLACK
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct ActiveTooltip {
|
||||
owner: u64,
|
||||
text: Box<str>,
|
||||
anchor: AnchorRect,
|
||||
placement: TooltipPlacement,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Tooltips {
|
||||
active: State<Option<ActiveTooltip>>,
|
||||
next_owner: Rc<Cell<u64>>,
|
||||
}
|
||||
|
||||
pub fn use_provide_tooltips() {
|
||||
let active = use_state(|| None::<ActiveTooltip>);
|
||||
let next_owner = use_hook(|| Rc::new(Cell::new(0u64)));
|
||||
use_provide_root_context(move || Tooltips { active, next_owner });
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AnchorState {
|
||||
tooltips: Tooltips,
|
||||
owner: u64,
|
||||
area: Rc<Cell<AnchorRect>>,
|
||||
generation: Rc<Cell<u64>>,
|
||||
text: Box<str>,
|
||||
placement: TooltipPlacement,
|
||||
}
|
||||
|
||||
pub struct TooltipAnchor(Option<AnchorState>);
|
||||
|
||||
pub fn use_tooltip_anchor(text: Option<Box<str>>, placement: TooltipPlacement) -> TooltipAnchor {
|
||||
let tooltips = use_hook(try_consume_root_context::<Tooltips>);
|
||||
let owner = use_hook(|| {
|
||||
tooltips.as_ref().map_or(0, |tooltips| {
|
||||
let owner = tooltips.next_owner.get() + 1;
|
||||
tooltips.next_owner.set(owner);
|
||||
owner
|
||||
})
|
||||
});
|
||||
let area = use_hook(|| Rc::new(Cell::new(AnchorRect::default())));
|
||||
let generation = use_hook(|| Rc::new(Cell::new(0u64)));
|
||||
|
||||
let abandoned = generation.clone();
|
||||
use_drop(move || abandoned.set(abandoned.get() + 1));
|
||||
|
||||
TooltipAnchor(tooltips.zip(text).map(|(tooltips, text)| AnchorState {
|
||||
tooltips,
|
||||
owner,
|
||||
area,
|
||||
generation,
|
||||
text,
|
||||
placement,
|
||||
}))
|
||||
}
|
||||
|
||||
impl TooltipAnchor {
|
||||
pub fn attach(&self, rect: Rect) -> Rect {
|
||||
let Some(state) = self.0.clone() else {
|
||||
return rect;
|
||||
};
|
||||
|
||||
let sized = state.clone();
|
||||
let enter = state.clone();
|
||||
let leave = state.clone();
|
||||
let pressed = state.clone();
|
||||
let scrolled = state;
|
||||
|
||||
rect.on_sized(move |e: Event<SizedEventData>| {
|
||||
let area = e.area;
|
||||
sized.area.set(AnchorRect {
|
||||
x: area.min_x(),
|
||||
y: area.min_y(),
|
||||
width: area.width(),
|
||||
height: area.height(),
|
||||
});
|
||||
})
|
||||
.on_pointer_enter(move |e: Event<PointerEventData>| {
|
||||
let global = e.global_location();
|
||||
let element = e.element_location();
|
||||
|
||||
let mut anchor = enter.area.get();
|
||||
anchor.x = (global.x - element.x) as f32;
|
||||
anchor.y = (global.y - element.y) as f32;
|
||||
|
||||
let generation = enter.generation.get() + 1;
|
||||
enter.generation.set(generation);
|
||||
|
||||
let pending = enter.generation.clone();
|
||||
let mut active = enter.tooltips.active;
|
||||
let tooltip = ActiveTooltip {
|
||||
owner: enter.owner,
|
||||
text: enter.text.clone(),
|
||||
anchor,
|
||||
placement: enter.placement,
|
||||
};
|
||||
|
||||
spawn(async move {
|
||||
tokio::time::sleep(SHOW_DELAY).await;
|
||||
if pending.get() == generation {
|
||||
active.set(Some(tooltip));
|
||||
}
|
||||
});
|
||||
})
|
||||
.on_pointer_leave(move |_| hide(&leave))
|
||||
.on_mouse_down(move |_| hide(&pressed))
|
||||
.on_wheel(move |_| hide(&scrolled))
|
||||
}
|
||||
}
|
||||
|
||||
fn hide(state: &AnchorState) {
|
||||
state.generation.set(state.generation.get() + 1);
|
||||
clear_owner(state.tooltips.active, state.owner);
|
||||
}
|
||||
|
||||
fn clear_owner(mut active: State<Option<ActiveTooltip>>, owner: u64) {
|
||||
let shown = active.peek().as_ref().map(|tooltip| tooltip.owner);
|
||||
if shown == Some(owner) {
|
||||
active.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct TooltipHost;
|
||||
|
||||
impl Component for TooltipHost {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let Some(tooltips) = use_hook(try_consume_root_context::<Tooltips>) else {
|
||||
return rect();
|
||||
};
|
||||
|
||||
let state = tooltips.active;
|
||||
let active = (*state.read()).clone();
|
||||
|
||||
let mut host = rect()
|
||||
.layer(Layer::OverlayLevel(OVERLAY_MAX_LEVEL))
|
||||
.position(Position::new_global().top(0.).left(0.))
|
||||
.width(Size::window_percent(100.))
|
||||
.height(Size::window_percent(100.))
|
||||
.interactive(false);
|
||||
|
||||
if let Some((owner, anchor)) = active.as_ref().map(|active| (active.owner, active.anchor)) {
|
||||
host = host.on_global_pointer_move(move |e: Event<PointerEventData>| {
|
||||
let cursor = e.global_location();
|
||||
if !anchor.contains(cursor.x as f32, cursor.y as f32) {
|
||||
clear_owner(state, owner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
host.maybe_child(active.map(|active| {
|
||||
let owner = active.owner;
|
||||
TooltipBubble {
|
||||
text: active.text,
|
||||
anchor: active.anchor,
|
||||
placement: active.placement,
|
||||
key: DiffKey::None,
|
||||
}
|
||||
.key(owner)
|
||||
.into_element()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct TooltipBubble {
|
||||
text: Box<str>,
|
||||
anchor: AnchorRect,
|
||||
placement: TooltipPlacement,
|
||||
key: DiffKey,
|
||||
}
|
||||
|
||||
impl KeyExt for TooltipBubble {
|
||||
fn write_key(&mut self) -> &mut DiffKey {
|
||||
&mut self.key
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TooltipBubble {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut size = use_state(Size2D::zero);
|
||||
|
||||
let measured = *size.read();
|
||||
let placed = measured.width > 0.;
|
||||
|
||||
let fade = use_animation(|conf| {
|
||||
conf.on_creation(OnCreation::Run);
|
||||
AnimNum::new(0., 1.).time(FADE_MS).ease(Ease::Out)
|
||||
});
|
||||
|
||||
let (x, y) = place(self.anchor, self.placement, measured);
|
||||
|
||||
rect()
|
||||
.position(Position::new_global().top(y).left(x))
|
||||
.interactive(false)
|
||||
.opacity(if placed { fade.read().value() } else { 0. })
|
||||
.max_width(Size::px(MAX_WIDTH))
|
||||
.padding(Gaps::new_symmetric(5., 9.))
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.background(colors::page_elevated())
|
||||
.border(border_all_color(1., colors::component_border()))
|
||||
.on_sized(move |e: Event<SizedEventData>| {
|
||||
let bubble = e.area.size;
|
||||
let previous = *size.peek();
|
||||
if (bubble.width - previous.width).abs() > 0.5
|
||||
|| (bubble.height - previous.height).abs() > 0.5
|
||||
{
|
||||
size.set(bubble);
|
||||
}
|
||||
})
|
||||
.child(
|
||||
label()
|
||||
.text(self.text.to_string())
|
||||
.font_size(12.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.max_lines(2)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_key(&self) -> DiffKey {
|
||||
self.key.clone().or(self.default_key())
|
||||
}
|
||||
}
|
||||
|
||||
fn place(anchor: AnchorRect, placement: TooltipPlacement, size: Size2D) -> (f32, f32) {
|
||||
let window = window_logical_size();
|
||||
|
||||
let centered_x = anchor.x + (anchor.width - size.width) / 2.;
|
||||
let centered_y = anchor.y + (anchor.height - size.height) / 2.;
|
||||
|
||||
let above = anchor.y - size.height - ANCHOR_GAP;
|
||||
let below = anchor.y + anchor.height + ANCHOR_GAP;
|
||||
let before = anchor.x - size.width - ANCHOR_GAP;
|
||||
let after = anchor.x + anchor.width + ANCHOR_GAP;
|
||||
|
||||
let fits_above = above >= 0.;
|
||||
let fits_below = below + size.height <= window.height;
|
||||
let fits_before = before >= 0.;
|
||||
let fits_after = after + size.width <= window.width;
|
||||
|
||||
let (x, y) = match placement {
|
||||
TooltipPlacement::Top => (
|
||||
centered_x,
|
||||
if fits_above || !fits_below { above } else { below },
|
||||
),
|
||||
TooltipPlacement::Bottom => (
|
||||
centered_x,
|
||||
if fits_below || !fits_above { below } else { above },
|
||||
),
|
||||
TooltipPlacement::Left => (
|
||||
if fits_before || !fits_after {
|
||||
before
|
||||
} else {
|
||||
after
|
||||
},
|
||||
centered_y,
|
||||
),
|
||||
TooltipPlacement::Right => (
|
||||
if fits_after || !fits_before {
|
||||
after
|
||||
} else {
|
||||
before
|
||||
},
|
||||
centered_y,
|
||||
),
|
||||
};
|
||||
|
||||
clamp_to_window(x, y, size.width, size.height)
|
||||
}
|
||||
|
|
@ -274,6 +274,7 @@ fn cluster_header(
|
|||
Button::new()
|
||||
.danger()
|
||||
.icon()
|
||||
.tooltip("Stop game")
|
||||
.on_press(move |_| kill_dispatch.kill_cluster(cluster_id))
|
||||
.child(Icon::new(IconType::Square).size(16.)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use freya::router::*;
|
|||
|
||||
use crate::components::{
|
||||
AccountSwitcher, ClusterUpdatePopup, GenericPromptOverlay, JavaPromptOverlay, OptionalModsPopup, MicrosoftJavaPromptOverlay,
|
||||
NotificationCenter, PackageUpdatePopup, SplashCurtain, StatusBar, Toasts,
|
||||
UpdatePromptOverlay,
|
||||
NotificationCenter, PackageUpdatePopup, SplashCurtain, StatusBar, Toasts, TooltipHost,
|
||||
UpdatePromptOverlay, use_provide_tooltips,
|
||||
};
|
||||
use crate::hooks::{SplashState, use_provide_overlay_claims, use_provide_splash};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
|
@ -24,6 +24,7 @@ impl Component for RootLayout {
|
|||
let home_ready = use_state(|| false);
|
||||
use_provide_splash(SplashState { active, home_ready });
|
||||
use_provide_overlay_claims();
|
||||
use_provide_tooltips();
|
||||
|
||||
// macOS rounds the window natively so Freya must not round on top of it
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
@ -83,6 +84,7 @@ impl Component for RootLayout {
|
|||
.child(OptionalModsPopup)
|
||||
.child(PackageUpdatePopup)
|
||||
.child(StatusBar)
|
||||
.child(TooltipHost)
|
||||
.child(SplashCurtain)
|
||||
.child(AnimationClockDriver)
|
||||
.child(HomeArtPrefetch)
|
||||
|
|
|
|||
|
|
@ -214,3 +214,14 @@ impl ImageFallbackExt for ImageViewer {
|
|||
self.error_renderer(move |_: String| placeholder.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_logical_size() -> Size2D {
|
||||
let platform = Platform::get();
|
||||
let scale = *platform.scale_factor.peek() as f32;
|
||||
let size = *platform.root_size.peek();
|
||||
if scale <= 0. {
|
||||
return size;
|
||||
}
|
||||
|
||||
Size2D::new(size.width / scale, size.height / scale)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ fn header_row(
|
|||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip("Close")
|
||||
.on_press(on_close)
|
||||
.child(Icon::new(IconType::XClose).size(18.)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ fn reset_button(overridden: bool, on_reset: EventHandler<()>) -> impl IntoElemen
|
|||
.small()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip(if overridden {
|
||||
"Reset to the global setting"
|
||||
} else {
|
||||
"Already using the global setting"
|
||||
})
|
||||
.corner_radius(CornerRadius::new_all(7.))
|
||||
.maybe(overridden, |el| el.on_press(move |_| on_reset.call(())))
|
||||
.enabled(overridden)
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ impl Component for FilterButton {
|
|||
Button::new()
|
||||
.secondary()
|
||||
.icon()
|
||||
.tooltip("Sort and filter")
|
||||
.width(Size::px(FILTER_BTN_W))
|
||||
.height(Size::px(34.))
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
|
|
|
|||
|
|
@ -523,6 +523,7 @@ impl Component for AccountRow {
|
|||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip("Refresh this account")
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
if *refreshing.peek() {
|
||||
|
|
@ -548,7 +549,12 @@ impl Component for AccountRow {
|
|||
.into_element()
|
||||
}))
|
||||
.child(
|
||||
Button::new().ghost().icon().enabled(false).child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.enabled(false)
|
||||
.tooltip("Edit")
|
||||
.child(
|
||||
Icon::new(IconType::Pencil01)
|
||||
.size(18.)
|
||||
.color(colors::fg_secondary()),
|
||||
|
|
@ -558,6 +564,7 @@ impl Component for AccountRow {
|
|||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.tooltip("Remove account")
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
remove.mutate(RemoveAccountKeys { id });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue