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:
aiongg authored and GitHub committed 2026-10-25 12:21:53 -04:00
1 parent da5bc8df94
commit 8b19c44267
9 files changed
+198 -44

No files matched your search

+9 -1
View File
@@ -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());
+81
View File
@@ -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());
}
+37 -11
View File
@@ -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");
}
+13 -3
View File
@@ -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()
+19 -5
View File
@@ -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) {
+4
View File
@@ -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
+14 -19
View File
@@ -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.
+18 -4
View File
@@ -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
View File
@@ -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