mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-05 10:10:46 +00:00
docking: a dragged window fades only while it highlights a drop target (#321)
* docking: a dragged native window fades only while a drop target is highlighted The dragged window stays opaque over empty space and goes to 75% only while the bar, only-tab or only-tab-row drag shows a drop preview. paint_group_drag reports whether it highlighted a target; end_cross keeps that for the next frame, since the dragged window sets its opacity before the preview is drawn. * docking: a dragged in-window floating window fades only while a drop target is highlighted The rule for native windows covers in-window floating windows too. A title bar drag draws its window at DRAG_OPACITY (75%) while the drag highlights a drop target, and opaque over empty space and after a drop, Escape or focus loss. finish_group_drag keeps paint_group_drag's result for the next frame, since the window draws before the preview. FloatingFrame takes an opacity for the whole frame, contents included. * docking: a title-bar drag also fades over a preview the host draws
This commit is contained in:
1 parent
da5bc8df94
commit
8b19c44267
9 files changed
+198
-44
No files matched your search
@@ -117,6 +117,7 @@ pub struct FloatingFrame<'a> {
|
||||
limits: PanelLimits,
|
||||
resizable: bool,
|
||||
resize_label: &'a str,
|
||||
opacity: f32,
|
||||
}
|
||||
|
||||
/// What a [`FloatingFrame`] did this frame.
|
||||
@@ -136,7 +137,7 @@ pub struct FrameResponse<R> {
|
||||
|
||||
impl<'a> FloatingFrame<'a> {
|
||||
pub fn new(id: Id, rect: Rect) -> Self {
|
||||
Self { id, rect, bounds: None, limits: PanelLimits::default(), resizable: true, resize_label: "Resize window" }
|
||||
Self { id, rect, bounds: None, limits: PanelLimits::default(), resizable: true, resize_label: "Resize window", opacity: 1.0 }
|
||||
}
|
||||
|
||||
/// The frame stays inside `bounds`; the default is the viewport's content rect.
|
||||
@@ -163,6 +164,12 @@ impl<'a> FloatingFrame<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Opacity of everything drawn in the frame, contents included (default 1.0).
|
||||
pub fn opacity(mut self, opacity: f32) -> Self {
|
||||
self.opacity = opacity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn layer(id: Id) -> LayerId {
|
||||
LayerId::new(Order::Middle, id)
|
||||
}
|
||||
@@ -180,6 +187,7 @@ impl<'a> FloatingFrame<'a> {
|
||||
.movable(false)
|
||||
.constrain_to(bounds);
|
||||
let shown = area.show(ctx, |ui| {
|
||||
ui.multiply_opacity(self.opacity);
|
||||
// Drawn at `rect` itself, also in egui's first (sizing) pass of a new area.
|
||||
ui.set_min_size(rect.size());
|
||||
ui.allocate_rect(rect, Sense::hover());
|
||||
|
||||
@@ -51,6 +51,8 @@ struct Content {
|
||||
/// Canvas 0 accepts side splits, as an app that opens panels beside it does.
|
||||
beside: bool,
|
||||
drawn: Vec<u32>,
|
||||
/// A preview the host draws for every drag, from `resolve_drag`.
|
||||
host_preview: Option<Rect>,
|
||||
}
|
||||
|
||||
impl DockContent<u32> for Content {
|
||||
@@ -71,6 +73,9 @@ impl DockContent<u32> for Content {
|
||||
fn allow_native(&self, _: &u32) -> bool {
|
||||
self.native
|
||||
}
|
||||
fn resolve_drag(&mut self, _: &Context, _: &DockStyle, _: &DropRequest<'_, u32>) -> Option<DropFeedback> {
|
||||
self.host_preview.map(|rect| DropFeedback { preview: Some(rect), insertion: None })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
@@ -106,6 +111,8 @@ struct Driver {
|
||||
hide: Hide,
|
||||
modifiers: Modifiers,
|
||||
time: f64,
|
||||
/// What the last frame painted.
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
@@ -119,6 +126,7 @@ impl Driver {
|
||||
hide: Hide::None,
|
||||
modifiers: Modifiers::NONE,
|
||||
time: 0.0,
|
||||
shapes: Vec::new(),
|
||||
};
|
||||
driver.step(vec![]);
|
||||
driver.step(vec![]);
|
||||
@@ -154,6 +162,7 @@ impl Driver {
|
||||
});
|
||||
let mut full = full;
|
||||
full.textures_delta.clear();
|
||||
self.shapes = full.shapes;
|
||||
output
|
||||
}
|
||||
|
||||
@@ -194,6 +203,22 @@ impl Driver {
|
||||
self.step(vec![button(at, false)])
|
||||
}
|
||||
|
||||
/// Opacity of the floating window that the last frame drew at `rect`: the alpha of its
|
||||
/// opaque background fill.
|
||||
fn window_opacity(&self, rect: [f32; 4]) -> Option<f32> {
|
||||
let rect = Rect::from_min_size(pos2(rect[0], rect[1]), vec2(rect[2], rect[3]));
|
||||
fn find(shape: &egui::Shape, rect: Rect) -> Option<f32> {
|
||||
match shape {
|
||||
egui::Shape::Vec(shapes) => shapes.iter().find_map(|s| find(s, rect)),
|
||||
egui::Shape::Rect(r) if r.rect.min.distance(rect.min) < 0.5 && r.rect.max.distance(rect.max) < 0.5 && r.fill.a() > 0 => {
|
||||
Some(f32::from(r.fill.a()) / 255.0)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
self.shapes.iter().find_map(|s| find(&s.shape, rect))
|
||||
}
|
||||
|
||||
fn wait(&mut self) {
|
||||
for _ in 0..45 {
|
||||
self.step(vec![]);
|
||||
@@ -728,3 +753,59 @@ fn undock_panel_group_floats_one_group_of_a_floating_window() {
|
||||
menu(&mut h, 5);
|
||||
assert_eq!(h.query_all_by_label("Undock Panel Group").count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_title_bar_drag_fades_over_a_preview_the_host_draws() {
|
||||
let mut d = Driver::new(workspace(), false);
|
||||
d.content.host_preview = Some(Rect::from_min_size(pos2(20.0, 20.0), vec2(100.0, 100.0)));
|
||||
let title = d.title(8).center();
|
||||
d.step(vec![Event::PointerMoved(title), button(title, true)]);
|
||||
d.step(vec![Event::PointerMoved(title + vec2(8.0, 6.0))]);
|
||||
d.step(vec![Event::PointerMoved(pos2(650.0, 150.0))]);
|
||||
let rect = window_of(&d.layout, 8).unwrap().rect;
|
||||
d.step(vec![]);
|
||||
let shown = d.window_opacity(rect).unwrap();
|
||||
assert!((shown - 0.75).abs() < 0.01, "{shown}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_in_window_floating_window_fades_only_while_its_title_bar_drag_highlights_a_drop_target() {
|
||||
let mut d = Driver::new(workspace(), false);
|
||||
let strip = d.tab(4).unwrap().center();
|
||||
let empty = pos2(650.0, 150.0);
|
||||
// Opacity of window W2 (dragged) and W1 once the pointer rests at `at`: the window draws
|
||||
// before the preview, so it fades a frame after the target lights up.
|
||||
let over = |d: &mut Driver, at: egui::Pos2| {
|
||||
d.step(vec![Event::PointerMoved(at)]);
|
||||
let (dragged, other) = (window_of(&d.layout, 8).unwrap().rect, window_of(&d.layout, 5).unwrap().rect);
|
||||
d.step(vec![]);
|
||||
(d.window_opacity(dragged).unwrap(), d.window_opacity(other).unwrap())
|
||||
};
|
||||
let shown = |d: &Driver| d.window_opacity(window_of(&d.layout, 8).unwrap().rect).unwrap();
|
||||
let grab = |d: &mut Driver| {
|
||||
let title = d.title(8).center();
|
||||
d.step(vec![Event::PointerMoved(title), button(title, true)]);
|
||||
d.step(vec![Event::PointerMoved(title + vec2(8.0, 6.0))]);
|
||||
};
|
||||
assert_eq!(shown(&d), 1.0);
|
||||
grab(&mut d);
|
||||
assert_eq!(over(&mut d, empty), (1.0, 1.0), "over empty space");
|
||||
let (dragged, other) = over(&mut d, strip);
|
||||
assert!((dragged - 0.75).abs() < 0.01 && other == 1.0, "over a drop target: {dragged}, {other}");
|
||||
assert_eq!(over(&mut d, empty).0, 1.0, "after leaving the target");
|
||||
assert!((over(&mut d, strip).0 - 0.75).abs() < 0.01);
|
||||
// Released off the target the window stays floating, opaque.
|
||||
d.step(vec![Event::PointerMoved(empty), button(empty, false)]);
|
||||
d.step(vec![]);
|
||||
assert_eq!(shown(&d), 1.0, "after the release");
|
||||
assert!(window_of(&d.layout, 8).is_some());
|
||||
|
||||
// Escape over a target puts the window back, opaque.
|
||||
grab(&mut d);
|
||||
assert!((over(&mut d, strip).0 - 0.75).abs() < 0.01);
|
||||
d.step(vec![Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }]);
|
||||
d.step(vec![]);
|
||||
assert_eq!(shown(&d), 1.0, "after Escape");
|
||||
d.step(vec![button(strip, false)]);
|
||||
assert!(window_of(&d.layout, 8).is_some());
|
||||
}
|
||||
@@ -2360,15 +2360,41 @@ fn a_tab_drag_onto_a_native_window_is_no_app_drag() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_native_window_fades_only_while_its_drag_can_dock() {
|
||||
let drag = |docks| WindowDrag { window: 1, grab: vec2(10.0, 10.0), to: pos2(100.0, 100.0), start: pos2(100.0, 100.0), docks };
|
||||
assert_eq!(drag_opacity(None, false, true), 1.0);
|
||||
// A bar drag that only moves the window stays opaque.
|
||||
assert_eq!(drag_opacity(Some(&drag(false)), false, false), 1.0);
|
||||
assert_eq!(drag_opacity(Some(&drag(false)), false, true), 0.75);
|
||||
// A drag by the window's only tab or group docks either way.
|
||||
assert_eq!(drag_opacity(Some(&drag(true)), false, false), 0.75);
|
||||
// With Cmd/Ctrl held the drag only moves the window.
|
||||
assert_eq!(drag_opacity(Some(&drag(false)), true, true), 1.0);
|
||||
assert_eq!(drag_opacity(Some(&drag(true)), true, false), 1.0);
|
||||
fn a_native_window_fades_only_while_its_bar_drag_highlights_a_drop_target() {
|
||||
let bar_to = |d: &mut DockDriver<u32, Content>, to: Pos2| {
|
||||
let id = window(d, 5).id;
|
||||
let local = d.window_local(id, ORIGIN + to.to_vec2()).unwrap();
|
||||
d.window_step(id, vec![Event::PointerMoved(local)]);
|
||||
drag_opacity(&d.ctx, d.id)
|
||||
};
|
||||
let grab = |d: &mut DockDriver<u32, Content>| {
|
||||
let id = window(d, 5).id;
|
||||
d.window_step(id, vec![Event::PointerMoved(pos2(40.0, 9.0)), button(pos2(40.0, 9.0), true)]);
|
||||
d.window_step(id, vec![Event::PointerMoved(pos2(60.0, 19.0))]);
|
||||
id
|
||||
};
|
||||
// A bar that only moves its window highlights nothing.
|
||||
let mut d = driver(layout(true)).with_style(|s| {
|
||||
s.command_press_floats = true;
|
||||
s.title_bar_docks = false;
|
||||
});
|
||||
grab(&mut d);
|
||||
assert_eq!(bar_to(&mut d, pos2(760.0, 300.0)), 1.0, "a move that cannot dock");
|
||||
|
||||
let mut d = driver(layout(true));
|
||||
let id = grab(&mut d);
|
||||
// Past the main window's right side: the desktop.
|
||||
assert_eq!(bar_to(&mut d, pos2(1000.0, 300.0)), 1.0, "over empty space");
|
||||
// The right edge zone of frame [3, 4].
|
||||
assert_eq!(bar_to(&mut d, pos2(760.0, 300.0)), 0.75, "over a drop target");
|
||||
// The middle of the frame is no target for a whole window.
|
||||
assert_eq!(bar_to(&mut d, pos2(600.0, 300.0)), 1.0, "after leaving the target");
|
||||
assert_eq!(bar_to(&mut d, pos2(760.0, 300.0)), 0.75);
|
||||
// With Cmd/Ctrl held the drop floats and nothing is highlighted.
|
||||
d.modifiers = Modifiers::COMMAND;
|
||||
assert_eq!(bar_to(&mut d, pos2(760.0, 300.0)), 1.0, "a floating drop");
|
||||
d.modifiers = Modifiers::NONE;
|
||||
assert_eq!(bar_to(&mut d, pos2(760.0, 300.0)), 0.75);
|
||||
d.window_step(id, vec![button(pos2(40.0, 9.0), false)]);
|
||||
assert_eq!(drag_opacity(&d.ctx, d.id), 1.0, "after the drop");
|
||||
}
|
||||
@@ -25,6 +25,9 @@ pub(super) const POINTER: &str = "drag-pointer";
|
||||
/// Key of an Escape that reached one of the application's windows other than the drag's
|
||||
/// owner. Keys go to the window with keyboard focus, which need not be the one dragging.
|
||||
const ESCAPE: &str = "drag-escape";
|
||||
/// Key of whether a native window's bar drag highlighted a drop target on the last frame, under
|
||||
/// the area's id: [`Cross::highlighted`] as [`Renderer::end_cross`] left it.
|
||||
pub(super) const HIGHLIGHTED: &str = "drag-highlighted";
|
||||
|
||||
/// The pointer of a drag, as the window that owns the drag last saw it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -99,6 +102,9 @@ pub(in crate::docking) struct Cross<P> {
|
||||
pub moved: Option<Pos2>,
|
||||
/// Resolving a window move that docks whatever `title_bar_docks` says.
|
||||
pub docks: bool,
|
||||
/// A native window's bar drag highlighted a drop target this frame, in whichever window
|
||||
/// drew its preview.
|
||||
pub highlighted: bool,
|
||||
/// A native window's dragged widget is one of the area's controls: egui keeps a drag per
|
||||
/// viewport.
|
||||
pub held: bool,
|
||||
@@ -122,6 +128,7 @@ impl<P> Default for Cross<P> {
|
||||
bar: false,
|
||||
moved: None,
|
||||
docks: false,
|
||||
highlighted: false,
|
||||
held: false,
|
||||
pointed: None,
|
||||
viewports: Vec::new(),
|
||||
@@ -315,7 +322,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let Some(drag) = pointer.window.and_then(|w| self.window_group_drag(w)) else { return };
|
||||
if let Some(Dest::Main(local)) = self.destination(&pointer, None) {
|
||||
(self.cross.bar, self.cross.docks) = (true, pointer.docks);
|
||||
self.paint_group_drag(ui, &drag, local, pointer.command);
|
||||
self.cross.highlighted |= self.paint_group_drag(ui, &drag, local, pointer.command);
|
||||
(self.cross.bar, self.cross.docks) = (false, false);
|
||||
}
|
||||
}
|
||||
@@ -371,7 +378,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
if let Some(window) = pointer.window {
|
||||
if let Some(drag) = self.window_group_drag(window) {
|
||||
self.cross.docks = pointer.docks;
|
||||
self.paint_group_drag(ui, &drag, local, pointer.command);
|
||||
self.cross.highlighted |= self.paint_group_drag(ui, &drag, local, pointer.command);
|
||||
self.cross.docks = false;
|
||||
}
|
||||
return;
|
||||
@@ -391,9 +398,12 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// End of the main window's frame: forget the pointer of a finished drag.
|
||||
/// End of the main window's frame: forget the pointer of a finished drag, and keep whether
|
||||
/// a bar drag highlighted a drop target. The dragged window sets its opacity from that on
|
||||
/// the next frame: it draws before the main window paints its preview.
|
||||
pub(in crate::docking) fn end_cross(&mut self, ctx: &Context) {
|
||||
let id = self.id;
|
||||
ctx.data_mut(|d| d.insert_temp(id.with(HIGHLIGHTED), self.cross.highlighted));
|
||||
let active = ctx.data(|d| {
|
||||
d.get_temp::<Drag<P>>(id.with("drag")).is_some()
|
||||
|| d.get_temp::<GroupDrag<P>>(id.with("group-drag")).is_some()
|
||||
|
||||
@@ -80,11 +80,13 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let movable = window.panels().all(|panel| self.permissions(panel).movable);
|
||||
let style = ChromeStyle::from_dock(self.style);
|
||||
let resize_label = self.style.resize_window_label.clone();
|
||||
let response = FloatingFrame::new(id, rect).bounds(screen).limits(limits).resizable(movable).resize_label(&resize_label).show(
|
||||
ctx,
|
||||
&style,
|
||||
|ui, contents| self.window_contents(ui, window, contents, rect, &style),
|
||||
);
|
||||
let opacity = self.drag_opacity(ctx, first);
|
||||
let response =
|
||||
FloatingFrame::new(id, rect).bounds(screen).limits(limits).resizable(movable).resize_label(&resize_label).opacity(opacity).show(
|
||||
ctx,
|
||||
&style,
|
||||
|ui, contents| self.window_contents(ui, window, contents, rect, &style),
|
||||
);
|
||||
for handle in &response.handles {
|
||||
self.register_interaction(ctx, handle.id);
|
||||
}
|
||||
@@ -104,6 +106,18 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opacity of the in-window floating window whose first panel is `first`: faded
|
||||
/// ([`super::DRAG_OPACITY`]) while its title bar drag highlights a drop target, as the last
|
||||
/// frame drew it ([`super::preview::GROUP_HIGHLIGHTED`]), else opaque. Its tabs and drag
|
||||
/// bars only drag a ghost, so the window stays opaque for them.
|
||||
fn drag_opacity(&self, ctx: &Context, first: &P) -> f32 {
|
||||
let moving = ctx
|
||||
.data(|d| d.get_temp::<GroupDrag<P>>(self.id.with("group-drag")))
|
||||
.is_some_and(|drag| drag.gesture == GroupGesture::Window && drag.panel == *first);
|
||||
let highlighted = ctx.data(|d| d.get_temp::<bool>(self.id.with(super::preview::GROUP_HIGHLIGHTED))).unwrap_or(false);
|
||||
if moving && highlighted { super::DRAG_OPACITY } else { 1.0 }
|
||||
}
|
||||
|
||||
/// The title bar, then the window's tree below it, in `contents`. Moves carry the whole
|
||||
/// `frame`, resize band included.
|
||||
fn window_contents(&mut self, ui: &mut Ui, window: &Floating<P>, contents: Rect, frame: Rect, style: &ChromeStyle) {
|
||||
|
||||
@@ -37,6 +37,10 @@ pub(in crate::docking) use native::{DesktopCursor, InputHook, set_input_hook};
|
||||
#[cfg(feature = "native-windows")]
|
||||
const BLOCKED_FOCUS_WAIT: f64 = 0.2;
|
||||
|
||||
/// Opacity of a floating window, in-window or native, while its bar, only tab or only tab row
|
||||
/// drags it over a drop target: the highlighted target under the window shows through it.
|
||||
pub(in crate::docking) const DRAG_OPACITY: f32 = 0.75;
|
||||
|
||||
/// While input is blocked (`starts`: from this frame), a native window of the dock area `area`
|
||||
/// with the keyboard would drop the keys the modal needs, so once, after
|
||||
/// [`BLOCKED_FOCUS_WAIT`], the main window, where the modal is, takes the keyboard if one still
|
||||
|
||||
@@ -95,21 +95,16 @@ pub(in crate::docking) const MACOS_CLOSE: CloseLayout =
|
||||
pub(in crate::docking) const OTHER_CLOSE: CloseLayout =
|
||||
CloseLayout { glyph_top: 4.5, glyph_trailing: 11.5, glyph_size: 9.0, hit_left: 4.5, hit_right: 4.5, hit_up: 4.5, hit_down: 4.5 };
|
||||
|
||||
/// Opacity of a native window while its bar or only tab drags it somewhere it can dock: the
|
||||
/// window stays above the main window, so the drop preview under the pointer shows only
|
||||
/// through it.
|
||||
/// Opacity of the native window that a bar drag of the area `area` moves, by the rule for every
|
||||
/// floating window ([`super::DRAG_OPACITY`]): faded while the drag highlights a drop target, as
|
||||
/// the last frame drew it ([`super::cross::HIGHLIGHTED`]), opaque over empty space, for a move
|
||||
/// that cannot dock and while Ctrl/Cmd keeps the drop floating. The window stays above the main
|
||||
/// window, so the highlighted target shows only through it.
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
const DRAG_OPACITY: f64 = 0.75;
|
||||
|
||||
/// Opacity of the window `drag` moves: faded while the drag can dock (its only tab or group, or
|
||||
/// a bar with [`crate::docking::DockStyle::title_bar_docks`]), opaque for a plain move and while
|
||||
/// Ctrl/Cmd keeps the drop floating (`float_only`).
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
pub(in crate::docking) fn drag_opacity(drag: Option<&WindowDrag>, float_only: bool, title_bar_docks: bool) -> f64 {
|
||||
match drag {
|
||||
Some(drag) if !float_only && (drag.docks || title_bar_docks) => DRAG_OPACITY,
|
||||
_ => 1.0,
|
||||
}
|
||||
pub(in crate::docking) fn drag_opacity(ctx: &Context, area: Id) -> f64 {
|
||||
let dragging = ctx.data(|d| d.get_temp::<WindowDrag>(area.with("window-drag"))).is_some();
|
||||
let highlighted = ctx.data(|d| d.get_temp::<bool>(area.with(super::cross::HIGHLIGHTED))).unwrap_or(false);
|
||||
if dragging && highlighted { f64::from(super::DRAG_OPACITY) } else { 1.0 }
|
||||
}
|
||||
|
||||
/// Sets the opacity of the AppKit windows titled `title`, never one titled `main` (the main
|
||||
@@ -645,17 +640,17 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
(place.known[0], place.known[1], place.pending) = (to.x, to.y, Some(now + MOVE_WAIT));
|
||||
}
|
||||
let moving = moving(ctx);
|
||||
// Set every frame from the drag state, so any way a drag ends (a drop, Escape, a focus
|
||||
// loss, the window closing) brings the window back to full opacity. AppKit finds the
|
||||
// window by title, so a window sharing the dragged one's title stays faded with it.
|
||||
// Set every frame from the drag state and the last frame's preview, so leaving a target
|
||||
// or any way a drag ends (a drop, Escape, a focus loss, the window closing) brings the
|
||||
// window back to full opacity. AppKit finds the window by title, so a window sharing
|
||||
// the dragged one's title stays faded with it.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let main = ctx.input(|i| i.raw.viewports.get(&ViewportId::ROOT).and_then(|v| v.title.clone()));
|
||||
let drag = ctx.data(|d| d.get_temp::<WindowDrag>(drag_key));
|
||||
let dragged = drag.as_ref().and_then(|drag| self.layout.floating.iter().find(|w| w.id == drag.window));
|
||||
let this = dragged.and_then(|w| crate::docking::dock_model::first_active(&w.root)).is_some_and(|p| self.label_of(p) == title);
|
||||
let float_only = drag.is_some_and(|drag| self.cross.pointer.is_some_and(|p| p.window == Some(drag.window) && p.command));
|
||||
let opacity = if this { drag_opacity(drag.as_ref(), float_only, self.style.title_bar_docks) } else { 1.0 };
|
||||
let opacity = if this { drag_opacity(ctx, self.id) } else { 1.0 };
|
||||
set_window_opacity(&title, main.as_deref(), opacity);
|
||||
}
|
||||
// The collapsed height a minimized window reports is not the size to keep.
|
||||
|
||||
@@ -33,6 +33,11 @@ const DOCKED_FLOAT_HEIGHT: f32 = 320.0;
|
||||
/// Least height of a floating window or flyout fitted to a short panel's content.
|
||||
pub(super) const MIN_FITTED_HEIGHT: f32 = 120.0;
|
||||
|
||||
/// Key of whether the area's group drag highlighted a drop target on the last frame, under the
|
||||
/// area's id. An in-window floating window that its bar drag moves fades from it on the next
|
||||
/// frame: the window draws before the preview.
|
||||
pub(super) const GROUP_HIGHLIGHTED: &str = "group-drag-highlighted";
|
||||
|
||||
/// What finishing a drag reads of the pointer, in the coordinates of the window finishing it.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) struct DragInput {
|
||||
@@ -71,6 +76,7 @@ impl DragInput {
|
||||
impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
pub(in crate::docking) fn finish_group_drag(&mut self, ui: &Ui) {
|
||||
let ctx = ui.ctx();
|
||||
ctx.data_mut(|d| d.remove::<bool>(self.id.with(GROUP_HIGHLIGHTED)));
|
||||
let key = self.id.with("group-drag");
|
||||
let Some(drag) = ctx.data(|d| d.get_temp::<GroupDrag<P>>(key)) else { return };
|
||||
if !drag.members.iter().all(|p| self.layout.contains(p) && self.permissions(p).movable) {
|
||||
@@ -122,7 +128,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
} else if let Some(pointer) = pointer.filter(|p| !self.blockers.iter().any(|r| r.contains(*p))) {
|
||||
self.output.group_drag = Some(drag.panel.clone());
|
||||
self.paint_group_drag(ui, &drag, pointer, float_only);
|
||||
if self.paint_group_drag(ui, &drag, pointer, float_only) {
|
||||
ctx.data_mut(|d| d.insert_temp(self.id.with(GROUP_HIGHLIGHTED), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +404,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
|
||||
/// The ghost at the pointer (a window moving live is its own ghost) under one destination
|
||||
/// preview. The ghost has a translucent fill, a 2 pt outline and the members' labels.
|
||||
pub(super) fn paint_group_drag(&mut self, ui: &Ui, drag: &GroupDrag<P>, pointer: Pos2, float_only: bool) {
|
||||
/// Returns whether it highlighted a drop target.
|
||||
pub(super) fn paint_group_drag(&mut self, ui: &Ui, drag: &GroupDrag<P>, pointer: Pos2, float_only: bool) -> bool {
|
||||
let ctx = ui.ctx();
|
||||
let painter = self.preview_painter(ctx);
|
||||
if drag.gesture != GroupGesture::Window {
|
||||
@@ -418,16 +427,17 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
}
|
||||
if float_only || (drag.gesture == GroupGesture::Window && !self.title_bar_docks()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if let Some((action, drop)) = self.dock_group_proposal(drag, pointer) {
|
||||
let highlighted = action.is_some();
|
||||
if let Some(action) = action {
|
||||
self.paint_dock_preview(ctx, &painter, action, &drag.members, &drop);
|
||||
if drag.gesture != GroupGesture::Window {
|
||||
self.paint_float_hint(ctx, pointer);
|
||||
}
|
||||
}
|
||||
return;
|
||||
return highlighted;
|
||||
}
|
||||
let proposal = self.group_proposal(drag, pointer).filter(|p| !p.noop);
|
||||
let zones = self.paint_group_zones(&painter, drag, pointer, proposal.as_ref());
|
||||
@@ -441,7 +451,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
if drag.gesture != GroupGesture::Window {
|
||||
self.paint_float_hint(ctx, pointer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub(in crate::docking) fn finish_drag(&mut self, ctx: &Context) {
|
||||
@@ -875,6 +887,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let painter = self.preview_painter(ctx);
|
||||
if let Some(rect) = feedback.preview.filter(|rect| crate::docking::targets::usable(*rect)) {
|
||||
drop_preview(&painter, rect, self.style.accent, true);
|
||||
// A window moving by its title bar fades over it, as over the renderer's own.
|
||||
ctx.data_mut(|d| d.insert_temp(self.id.with(GROUP_HIGHLIGHTED), true));
|
||||
}
|
||||
if let Some(rect) = feedback.insertion.filter(|rect| crate::docking::targets::usable(*rect)) {
|
||||
painter.rect_filled(rect, 0.0, self.style.accent);
|
||||
|
||||
+3
-1
@@ -215,7 +215,9 @@ A flyout fits its content the same way, without the title bar.
|
||||
- A tab drags one panel. A group's drag bar (the space beside its tabs) or its top border drags
|
||||
the whole group. A column bar drags a column. A floating window's title bar drags the window;
|
||||
with `title_bar_docks` a release over a target docks the whole window there, else it only
|
||||
moves.
|
||||
moves. While the drag highlights a drop target, the window is drawn at 75% opacity so the
|
||||
target shows through it; over empty space, and once the drag ends, it is opaque. A native
|
||||
window moved by its only tab or tab row fades the same way.
|
||||
- Inside a floating window, tabs and drag bars drag and dock as they do in the docked layout,
|
||||
into any window. A drag bar or border of a window's only group takes the whole window along,
|
||||
so, like the title bar, it looks through the window: the groups, docks and windows behind it
|
||||
|
||||
Reference in new issue
Block a user