T09b overlays: lightbox, spotlight and tour, floating toolbar, peek; images fill their box; overlays take focus back when its element leaves the tree
This commit is contained in:
18 files changed
+1754
-228
No files matched your search
@@ -0,0 +1,169 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
AnimationExt, AnyElement, App, Corner, ElementId, Entity, FontWeight, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, SharedString, Styled, Window, anchored, canvas,
|
||||
deferred, div, point, prelude::*,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
buttons::{ButtonVariant, IconButton},
|
||||
forms::{Run, TextInput, surface},
|
||||
layout::Collapsible,
|
||||
motion,
|
||||
primitives::IconName,
|
||||
theme::{ActiveTheme, ControlSize, TextSize},
|
||||
};
|
||||
|
||||
/// Tools that float over a text field's selection while it has one and holds focus. Its buttons leave focus in the field.
|
||||
#[derive(IntoElement)]
|
||||
pub struct FloatingToolbar {
|
||||
id: ElementId,
|
||||
field: Entity<TextInput>,
|
||||
tools: SmallVec<[AnyElement; 4]>,
|
||||
}
|
||||
|
||||
impl FloatingToolbar {
|
||||
pub fn new(id: impl Into<ElementId>, field: &Entity<TextInput>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
field: field.clone(),
|
||||
tools: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for FloatingToolbar {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.tools.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for FloatingToolbar {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let seen = window.use_keyed_state((self.id.clone(), "seen"), cx, |_, _| None);
|
||||
let input = self.field.read(cx);
|
||||
let selection = input.selection();
|
||||
let shown = !selection.is_empty() && input.focus().is_focused(window);
|
||||
let start = input.bounds_for(selection.start);
|
||||
let anchor = start.filter(|_| shown);
|
||||
let (field, from) = (self.field.clone(), selection.start);
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.child(canvas(
|
||||
|_, _, _| {},
|
||||
move |_, _, _, cx| {
|
||||
let now = field.read(cx).bounds_for(from);
|
||||
if now != start {
|
||||
log::debug!("floating toolbar: the selection moved to {now:?}");
|
||||
seen.update(cx, |seen, cx| {
|
||||
*seen = now;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
},
|
||||
))
|
||||
.when_some(anchor, |host, anchor| {
|
||||
let lift = cx.theme().float_gap().to_pixels(window.rem_size());
|
||||
host.child(
|
||||
deferred(
|
||||
anchored()
|
||||
.position(anchor.origin - point(gpui::Pixels::ZERO, lift))
|
||||
.anchor(Corner::BottomLeft)
|
||||
.snap_to_window()
|
||||
.child(
|
||||
surface((self.id.clone(), "bar"), cx)
|
||||
.occlude()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_0p5()
|
||||
.p_1()
|
||||
.children(self.tools)
|
||||
.with_animation(
|
||||
(self.id.clone(), "in"),
|
||||
gpui::Animation::new(motion::duration(motion::FAST, cx))
|
||||
.with_easing(motion::ease_out_cubic),
|
||||
|bar, t| bar.opacity(t).mb(motion::NUDGE * (1.0 - t)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.with_priority(1),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A panel that opens in the flow under a line, the way an editor peeks at a definition: a title bar with a close button over the owner's content.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Peek {
|
||||
id: ElementId,
|
||||
title: SharedString,
|
||||
open: bool,
|
||||
body: SmallVec<[AnyElement; 2]>,
|
||||
on_close: Run,
|
||||
}
|
||||
|
||||
impl Peek {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
title: impl Into<SharedString>,
|
||||
open: bool,
|
||||
on_close: impl Fn(&mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
title: title.into(),
|
||||
open,
|
||||
body: SmallVec::new(),
|
||||
on_close: Rc::new(on_close),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Peek {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.body.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Peek {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let colors = &theme.colors;
|
||||
let (id, close) = (self.id.clone(), self.on_close);
|
||||
Collapsible::new(self.id.clone(), self.open).child(
|
||||
div()
|
||||
.my_2()
|
||||
.border_l_2()
|
||||
.border_color(colors.accent)
|
||||
.bg(colors.surface)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.h(theme.control_height(ControlSize::Lg))
|
||||
.pl_3()
|
||||
.pr_1()
|
||||
.border_b_1()
|
||||
.border_color(colors.border)
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.child(self.title)
|
||||
.child(
|
||||
IconButton::new((self.id.clone(), "close"), IconName::X)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.tooltip("Close")
|
||||
.on_click(move |_, window, cx| {
|
||||
log::info!("peek {id:?}: closed");
|
||||
close(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(div().p_3().children(self.body)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user