refactor(viewport): migrate model-space tiling to iced pane_grid
Replace the custom model_tiles tiling with iced 0.14 pane_grid as the layout source of truth, while paper-space floating viewports are left untouched (they overlap/free-place, which pane_grid cannot model). Each Model pane is its own shader widget rendering its camera into the pane bounds; the renderer Primitive carries a base_slot so several shader widgets share the type-keyed MultiPipeline storage on disjoint slots. A second pane_grid of transparent mouse_areas (plus on_resize) layers above the crosshair overlay to receive input — the overlay's Hidden cursor interaction otherwise levitates the cursor and starves any layer beneath it. The active pane follows the cursor (hover-activate), and grid / crosshair / UCS icon render per pane while the ViewCube and render-mode picker stay on the active pane only. Split / close / VPORTS presets and the VPort table round-trip now drive pane_grid (guillotine reconstruction on load). Adds drag-to-swap via a controls bar handle with a ghost-card preview, visible pane dividers from the pane_grid split regions, a default model background of RGB(33,40,48), and removes all the superseded custom-tile code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8bdf5228d2
commit
495b13ec4f
18 changed files with 1004 additions and 733 deletions
|
|
@ -370,31 +370,33 @@ impl OpenCADStudio {
|
|||
.push_info("VPORTS Configuration [SIngle/2H/2V/4]:");
|
||||
return Some(self.focus_cmd_input());
|
||||
}
|
||||
// Model space: split the tiled viewport layout.
|
||||
use iced::Rectangle as R;
|
||||
let full = R { x: 0.0, y: 0.0, width: 1.0, height: 1.0 };
|
||||
let rects: Option<Vec<R>> = match sub.as_str() {
|
||||
"SINGLE" | "SI" | "1" => Some(vec![full]),
|
||||
"2H" | "2" => Some(vec![
|
||||
R { x: 0.0, y: 0.0, width: 1.0, height: 0.5 },
|
||||
R { x: 0.0, y: 0.5, width: 1.0, height: 0.5 },
|
||||
]),
|
||||
"2V" => Some(vec![
|
||||
R { x: 0.0, y: 0.0, width: 0.5, height: 1.0 },
|
||||
R { x: 0.5, y: 0.0, width: 0.5, height: 1.0 },
|
||||
]),
|
||||
"4" => Some(vec![
|
||||
R { x: 0.0, y: 0.0, width: 0.5, height: 0.5 },
|
||||
R { x: 0.5, y: 0.0, width: 0.5, height: 0.5 },
|
||||
R { x: 0.0, y: 0.5, width: 0.5, height: 0.5 },
|
||||
R { x: 0.5, y: 0.5, width: 0.5, height: 0.5 },
|
||||
]),
|
||||
// Model space: split the tiled viewport layout via pane_grid.
|
||||
use iced::widget::pane_grid::{Axis, Configuration as C};
|
||||
let split = |axis, a, b| C::Split {
|
||||
axis,
|
||||
ratio: 0.5,
|
||||
a: Box::new(a),
|
||||
b: Box::new(b),
|
||||
};
|
||||
let config: Option<(C<usize>, usize)> = match sub.as_str() {
|
||||
"SINGLE" | "SI" | "1" => Some((C::Pane(0), 1)),
|
||||
"2H" | "2" => {
|
||||
Some((split(Axis::Horizontal, C::Pane(0), C::Pane(1)), 2))
|
||||
}
|
||||
"2V" => Some((split(Axis::Vertical, C::Pane(0), C::Pane(1)), 2)),
|
||||
"4" => Some((
|
||||
split(
|
||||
Axis::Vertical,
|
||||
split(Axis::Horizontal, C::Pane(0), C::Pane(2)),
|
||||
split(Axis::Horizontal, C::Pane(1), C::Pane(3)),
|
||||
),
|
||||
4,
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
match rects {
|
||||
Some(rects) => {
|
||||
let n = rects.len();
|
||||
self.tabs[i].scene.set_model_tile_layout(rects);
|
||||
match config {
|
||||
Some((config, n)) => {
|
||||
self.tabs[i].scene.set_model_panes(config);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.command_line
|
||||
.push_output(&format!("VPORTS: {n} viewport(s)."));
|
||||
|
|
|
|||
|
|
@ -26,18 +26,7 @@ pub use style_ops::StyleKind;
|
|||
use document::DocumentTab;
|
||||
|
||||
use crate::modules::ModuleEvent;
|
||||
use crate::scene::{CubeRegion, TileEdgeOrient};
|
||||
|
||||
/// In-flight drag of a Model-tile inner divider. `last_applied` is the
|
||||
/// most recent normalized coordinate handed to `move_model_tile_edge` —
|
||||
/// the next `ViewportMove` uses it as the `old_coord` argument so the
|
||||
/// drag follows the same edge across frames even after sub-pixel
|
||||
/// adjustments from the clamp inside `move_model_tile_edge`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TileDrag {
|
||||
pub orient: TileEdgeOrient,
|
||||
pub last_applied: f32,
|
||||
}
|
||||
use crate::scene::CubeRegion;
|
||||
|
||||
/// Which UCS-icon grip is being dragged. `Origin` slides the UCS origin within
|
||||
/// its own plane; `XAxis`/`YAxis` rotate the UCS so that axis points at the
|
||||
|
|
@ -265,10 +254,6 @@ pub(super) struct OpenCADStudio {
|
|||
/// `true` after a bare `VPORTS` in model space — the next command-line
|
||||
/// entry is treated as the tiled-config option (SIngle/2H/2V/4).
|
||||
awaiting_vports: bool,
|
||||
/// Active drag of a Model-tile divider. Set on press over an inner
|
||||
/// edge, updated on move, cleared on release (which also runs the
|
||||
/// collapse pass).
|
||||
tile_drag: Option<TileDrag>,
|
||||
/// Cursor is hovering over the UCS icon body — drives the hover highlight.
|
||||
ucs_icon_hover: bool,
|
||||
/// UCS icon is selected (clicked): its grips are shown and draggable.
|
||||
|
|
@ -276,6 +261,9 @@ pub(super) struct OpenCADStudio {
|
|||
/// Active direct-drag of a UCS icon grip (origin slide or axis rotate).
|
||||
/// Set on press over a grip, updated on move, committed on release.
|
||||
ucs_grip_drag: Option<UcsGripKind>,
|
||||
/// Pane-move drag in progress: the source pane's tile index, armed by the
|
||||
/// controls-bar drag handle. On release over another pane the two swap.
|
||||
pane_move_from: Option<usize>,
|
||||
/// `true` once the user has reshaped the dynamic-input field set via
|
||||
/// the `,` separator during the current command iteration. Tells
|
||||
/// `sync_dyn_fields` to preserve the user's chosen shape instead of
|
||||
|
|
@ -1143,6 +1131,26 @@ pub enum Message {
|
|||
ViewportMiddleRelease,
|
||||
ViewportScroll(mouse::ScrollDelta),
|
||||
ViewportExit,
|
||||
// ── Per-pane Model viewport (pane_grid) ───────────────────────────────
|
||||
/// A pane_grid divider was dragged — resize the split.
|
||||
PaneResized(iced::widget::pane_grid::ResizeEvent),
|
||||
/// A pane body was clicked — focus that pane.
|
||||
PaneClicked(iced::widget::pane_grid::Pane),
|
||||
/// A pane was drag-and-dropped onto another — swap them.
|
||||
PaneDragged(iced::widget::pane_grid::DragEvent),
|
||||
/// Per-pane mouse events. `usize` = the pane's tile index; the `Point` is
|
||||
/// pane-local (offset to canvas coords + focus in the handler).
|
||||
PaneMove(usize, Point),
|
||||
PanePress(usize),
|
||||
PaneRelease(usize),
|
||||
PaneRightPress(usize),
|
||||
PaneRightRelease(usize),
|
||||
PaneMiddlePress(usize),
|
||||
PaneMiddleRelease(usize),
|
||||
PaneScroll(usize, mouse::ScrollDelta),
|
||||
/// Drag-handle pressed on the active pane's controls bar: arm a pane move —
|
||||
/// the next pane released over is swapped with the active pane.
|
||||
PaneMoveStart,
|
||||
ViewCubeSnap(CubeRegion),
|
||||
/// World-frame view snap from a compass cardinal (N/E/S/W), bypassing the
|
||||
/// UCS so the compass stays world-aligned.
|
||||
|
|
@ -1806,10 +1814,10 @@ impl OpenCADStudio {
|
|||
default_bg_color: None,
|
||||
default_paper_bg_color: None,
|
||||
awaiting_vports: false,
|
||||
tile_drag: None,
|
||||
ucs_icon_hover: false,
|
||||
ucs_icon_selected: false,
|
||||
ucs_grip_drag: None,
|
||||
pane_move_from: None,
|
||||
dyn_user_reshaped: false,
|
||||
grip_hover: None,
|
||||
grip_popup: None,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
@ -402,6 +402,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.text_inline_cancel();
|
||||
return self.post_editor_closed(false);
|
||||
}
|
||||
// Esc cancels an armed pane move.
|
||||
if self.pane_move_from.take().is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
// UCS icon: Esc ends any grip drag and clears the selection
|
||||
// (only when no command owns Escape).
|
||||
if self.tabs[self.active_tab].active_cmd.is_none()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use super::{Message, OpenCADStudio};
|
||||
use crate::scene::{
|
||||
VIEWCUBE_DRAW_PX, VIEWCUBE_PAD,
|
||||
};
|
||||
use crate::scene::VIEWCUBE_DRAW_PX;
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
use iced::time::Instant;
|
||||
|
|
@ -42,19 +40,6 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
|
|||
}
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX;
|
||||
/// Pixel distance from a Model-tile inner divider that still registers as
|
||||
/// a resize grip on the press.
|
||||
const TILE_EDGE_HIT_PX: f32 = 4.0;
|
||||
/// Normalized minimum tile size a divider drag clamps to. Sized to
|
||||
/// comfortably contain the ViewCube + its padding so every tile always
|
||||
/// has room to show the gizmo.
|
||||
fn tile_min_norm(canvas_w: f32, canvas_h: f32) -> (f32, f32) {
|
||||
let px = VIEWCUBE_DRAW_PX + 2.0 * VIEWCUBE_PAD + 16.0;
|
||||
(
|
||||
(px / canvas_w.max(1.0)).min(0.95),
|
||||
(px / canvas_h.max(1.0)).min(0.95),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
const KB: f64 = 1024.0;
|
||||
|
|
@ -706,15 +691,17 @@ impl OpenCADStudio {
|
|||
|
||||
Message::SplitModelViewport(horizontal) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.split_active_model_tile(horizontal);
|
||||
self.tabs[i].scene.split_active_pane(horizontal);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CloseModelViewport => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.close_active_model_tile();
|
||||
self.tabs[i].scene.close_active_pane();
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
self.adopt_view_display(i);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
@ -1021,6 +1008,69 @@ impl OpenCADStudio {
|
|||
|
||||
Message::ViewportExit => self.on_viewport_exit(),
|
||||
|
||||
// ── Per-pane Model viewport ───────────────────────────────────
|
||||
Message::PaneResized(ev) => self.on_pane_resized(ev),
|
||||
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
|
||||
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
|
||||
Message::PaneMove(idx, local) => {
|
||||
let p = self.pane_canvas_point(idx, local);
|
||||
// While dragging a pane, just track the cursor (no focus swap or
|
||||
// snap) so the drop target reads cleanly.
|
||||
if self.pane_move_from.is_some() {
|
||||
self.tabs[self.active_tab]
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.last_move_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_move(p)
|
||||
}
|
||||
Message::PaneMoveStart => {
|
||||
let i = self.active_tab;
|
||||
self.pane_move_from = Some(self.tabs[i].scene.active_model_tile.get());
|
||||
Task::none()
|
||||
}
|
||||
Message::PanePress(idx) => {
|
||||
// A fresh press ends any stale (un-dropped) pane move.
|
||||
self.pane_move_from = None;
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_left_press()
|
||||
}
|
||||
Message::PaneRelease(idx) => {
|
||||
// Finishing a pane-move drag: swap the source pane with the one
|
||||
// released over, instead of the normal release handling.
|
||||
if let Some(from) = self.pane_move_from.take() {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.swap_model_panes(from, idx);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
return Task::none();
|
||||
}
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_left_release()
|
||||
}
|
||||
Message::PaneRightPress(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportRightPress)
|
||||
}
|
||||
Message::PaneRightRelease(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportRightRelease)
|
||||
}
|
||||
Message::PaneMiddlePress(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportMiddlePress)
|
||||
}
|
||||
Message::PaneMiddleRelease(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportMiddleRelease)
|
||||
}
|
||||
Message::PaneScroll(idx, d) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportScroll(d))
|
||||
}
|
||||
|
||||
Message::ViewportLeftPress => self.on_viewport_left_press(),
|
||||
|
||||
Message::ViewportLeftRelease => self.on_viewport_left_release(),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(unused_imports)]
|
||||
use super::util::*;
|
||||
use super::{format_size, tile_min_norm, TILE_EDGE_HIT_PX, VIEWCUBE_HIT_SIZE};
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
|
|
@ -12,7 +12,7 @@ use crate::modules::ModuleEvent;
|
|||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, TileEdgeOrient, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
};
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
|
|
@ -480,34 +480,6 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
|||
pub(super) fn on_viewport_move(&mut self, p: Point) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
|
||||
// A Model-tile divider drag short-circuits the rest of
|
||||
// the move handling — the cursor neither pans the camera
|
||||
// nor updates snap state while the user is resizing
|
||||
// panes.
|
||||
if let Some(drag) = self.tile_drag.as_mut() {
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
if vw > 1.0 && vh > 1.0 {
|
||||
let new_coord = match drag.orient {
|
||||
TileEdgeOrient::Vertical => (p.x / vw).clamp(0.0, 1.0),
|
||||
TileEdgeOrient::Horizontal => (p.y / vh).clamp(0.0, 1.0),
|
||||
};
|
||||
let (min_w, min_h) = tile_min_norm(vw, vh);
|
||||
let min_size = match drag.orient {
|
||||
TileEdgeOrient::Vertical => min_w,
|
||||
TileEdgeOrient::Horizontal => min_h,
|
||||
};
|
||||
self.tabs[i].scene.move_model_tile_edge(
|
||||
drag.orient,
|
||||
drag.last_applied,
|
||||
new_coord,
|
||||
min_size,
|
||||
);
|
||||
drag.last_applied = new_coord;
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// UCS icon grip drag: map the cursor onto the UCS plane and
|
||||
// slide the origin / rotate the axis. Short-circuits pan & snap.
|
||||
if let Some(kind) = self.ucs_grip_drag {
|
||||
|
|
@ -669,21 +641,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
|||
let vp_size = sel.vp_size;
|
||||
drop(sel);
|
||||
|
||||
// Hover (no button held): the tile under the cursor becomes
|
||||
// active, so the camera + tile bounds used for picking below
|
||||
// follow the pane the cursor is in. During a drag the active
|
||||
// tile stays put so the operation finishes in its own pane.
|
||||
if !dragging && vp_size.0 > 1.0 && vp_size.1 > 1.0 {
|
||||
if self.tabs[i]
|
||||
.scene
|
||||
.set_active_model_tile_at(p.x / vp_size.0, p.y / vp_size.1)
|
||||
{
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
// Grid/snap follow the newly active viewport.
|
||||
self.adopt_view_display(i);
|
||||
}
|
||||
}
|
||||
// The active pane already follows the cursor (each pane's own
|
||||
// mouse_area focuses it via `PaneMove` → `focus_model_pane`), so
|
||||
// the camera + tile bounds used for picking below are already the
|
||||
// pane the cursor is in.
|
||||
|
||||
// Tile-relative picking: shadow `p` with the cursor mapped
|
||||
// into the active Model tile and `vp_size` with the tile's
|
||||
|
|
@ -1398,6 +1359,65 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
|||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
|
||||
// ── Per-pane Model viewport (pane_grid) ───────────────────────────────
|
||||
|
||||
/// Focus Model pane `idx` (cursor entered it): swap in its camera, sync the
|
||||
/// render-mode / grid display. No-op if already active.
|
||||
pub(super) fn focus_model_pane(&mut self, idx: usize) {
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].scene.set_active_model_tile(idx) {
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
self.adopt_view_display(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a pane-local cursor point (from the pane's `mouse_area`) to the
|
||||
/// canvas-relative point the viewport handlers expect.
|
||||
pub(super) fn pane_canvas_point(&self, idx: usize, local: Point) -> Point {
|
||||
let i = self.active_tab;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let o = self.tabs[i].scene.pane_origin_px(idx, vw, vh);
|
||||
Point::new(o.x + local.x, o.y + local.y)
|
||||
}
|
||||
|
||||
pub(super) fn on_pane_resized(
|
||||
&mut self,
|
||||
ev: iced::widget::pane_grid::ResizeEvent,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.model_panes.resize(ev.split, ev.ratio);
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
self.tabs[i].scene.sync_tiles_from_panes(vw, vh);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_pane_clicked(
|
||||
&mut self,
|
||||
pane: iced::widget::pane_grid::Pane,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
if let Some(&idx) = self.tabs[i].scene.model_panes.get(pane) {
|
||||
self.focus_model_pane(idx);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_pane_dragged(
|
||||
&mut self,
|
||||
ev: iced::widget::pane_grid::DragEvent,
|
||||
) -> Task<Message> {
|
||||
if let iced::widget::pane_grid::DragEvent::Dropped { pane, target } = ev {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.model_panes.drop(pane, target);
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
self.tabs[i].scene.sync_tiles_from_panes(vw, vh);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_viewport_left_press(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
// A click in the viewport dismisses any open ribbon dropdown
|
||||
|
|
@ -1497,48 +1517,9 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
|||
self.ucs_icon_selected = false;
|
||||
}
|
||||
|
||||
// Tiled Model layout: an inner divider near the cursor
|
||||
// becomes a resize grip. Start the drag and short-circuit
|
||||
// (no pick / select / camera swap). Released on
|
||||
// `ViewportLeftRelease`.
|
||||
if self.tabs[i].active_cmd.is_none()
|
||||
&& self.tabs[i].scene.current_layout == "Model"
|
||||
&& vw > 1.0
|
||||
&& vh > 1.0
|
||||
{
|
||||
let canvas_bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: vw,
|
||||
height: vh,
|
||||
};
|
||||
if let Some(edge) =
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.hit_model_tile_edge(p, canvas_bounds, TILE_EDGE_HIT_PX)
|
||||
{
|
||||
self.tile_drag = Some(crate::app::TileDrag {
|
||||
orient: edge.orient,
|
||||
last_applied: edge.coord,
|
||||
});
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
// Tiled Model layout: clicking a non-active tile activates
|
||||
// it (swapping in its camera) instead of selecting / drawing.
|
||||
if self.tabs[i].active_cmd.is_none() && vw > 1.0 && vh > 1.0 {
|
||||
if self.tabs[i]
|
||||
.scene
|
||||
.set_active_model_tile_at(p.x / vw, p.y / vh)
|
||||
{
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
// Grid/snap follow the newly active viewport.
|
||||
self.adopt_view_display(i);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
// Divider resize is handled natively by the input pane_grid
|
||||
// (`on_resize`); the active pane already follows the cursor via
|
||||
// `focus_model_pane`. So a press here goes straight to picking.
|
||||
|
||||
// From here the click targets the active tile: map the
|
||||
// cursor into it and use the tile's size for picking, so
|
||||
|
|
@ -1649,14 +1630,6 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
|||
return Task::none();
|
||||
}
|
||||
|
||||
// End an in-flight tile-divider drag. The move clamps
|
||||
// each side to its minimum, so panes never collapse from
|
||||
// dragging — use the close button to remove a pane.
|
||||
if self.tile_drag.take().is_some() {
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// Commit a UCS icon grip drag: persist the new UCS so it
|
||||
// round-trips, and clear the lingering press state.
|
||||
if self.ucs_grip_drag.take().is_some() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use super::*;
|
|||
use super::super::document::{DynComponent, DynFieldEntry};
|
||||
use super::super::Message;
|
||||
use iced::widget::{
|
||||
button, container, row,
|
||||
button, container, mouse_area, row,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Theme};
|
||||
|
||||
|
|
@ -25,6 +25,33 @@ pub(super) fn viewport_controls<'a>(
|
|||
];
|
||||
let light = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
||||
let accent = Color { r: 0.45, g: 0.70, b: 1.0, a: 1.0 };
|
||||
let green = Color { r: 0.36, g: 0.80, b: 0.45, a: 1.0 };
|
||||
let red = Color { r: 0.92, g: 0.38, b: 0.38, a: 1.0 };
|
||||
|
||||
// Fixed-colour icon button (close = red); colour stays on hover.
|
||||
let tinted_btn = move |bytes: &'static [u8], color: Color, msg: Message| {
|
||||
button(crate::ui::icons::tinted(bytes, 15.0, color))
|
||||
.on_press(msg)
|
||||
.padding([4, 6])
|
||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
iced::widget::button::Status::Hovered
|
||||
| iced::widget::button::Status::Pressed => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 0.9,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: color,
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
|
||||
// Borderless icon button; an `active` toggle gets an accent tint + fill.
|
||||
let icon_btn = move |bytes: &'static [u8], active: bool, msg: Message| {
|
||||
|
|
@ -106,11 +133,29 @@ pub(super) fn viewport_controls<'a>(
|
|||
.push(icon_btn(crate::ui::icons::SPLIT_V, false, Message::SplitModelViewport(false)))
|
||||
.push(sep())
|
||||
.push(icon_btn(crate::ui::icons::SPLIT_H, false, Message::SplitModelViewport(true)));
|
||||
// Close button: only meaningful with more than one model tile.
|
||||
// Drag handle + close: only meaningful with more than one model tile.
|
||||
// The handle is a `mouse_area` (not a button) so it fires on press-DOWN,
|
||||
// letting the drag continue onto the target pane to swap them (a button
|
||||
// would only fire on release). Placed just left of Close.
|
||||
if tile_count > 1 {
|
||||
let drag = mouse_area(
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::MOVE, 15.0, green))
|
||||
.padding([4, 6])
|
||||
.style(|_: &Theme| iced::widget::container::Style {
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.interaction(iced::mouse::Interaction::Grab)
|
||||
.on_press(Message::PaneMoveStart);
|
||||
bar = bar
|
||||
.push(sep())
|
||||
.push(icon_btn(crate::ui::icons::CLOSE, false, Message::CloseModelViewport));
|
||||
.push(drag)
|
||||
.push(sep())
|
||||
.push(tinted_btn(crate::ui::icons::CLOSE, red, Message::CloseModelViewport));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::scene::pick::grip::{grips_to_screen, grips_to_screen_paper, grips_to_
|
|||
use crate::scene::view::viewport_pane::ViewportPane;
|
||||
use crate::scene::{VIEWCUBE_PAD, VIEWCUBE_REGION_PX};
|
||||
use iced::widget::{
|
||||
button, column, container, mouse_area, row, shader, stack, text, Row,
|
||||
button, column, container, mouse_area, pane_grid, responsive, row, shader, stack, text, Row,
|
||||
Space,
|
||||
};
|
||||
use iced::window;
|
||||
|
|
@ -78,7 +78,7 @@ impl OpenCADStudio {
|
|||
// borders) and the floating content viewports blit on top.
|
||||
let viewport_3d: Element<'_, Message> = if tab.is_start {
|
||||
start_page_view()
|
||||
} else {
|
||||
} else if is_paper {
|
||||
shader(ViewportPane::model(
|
||||
&tab.scene,
|
||||
self.show_viewcube,
|
||||
|
|
@ -87,6 +87,67 @@ impl OpenCADStudio {
|
|||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
// Model space: a pane_grid of per-pane shader widgets (rendering
|
||||
// only). The input mouse_areas live in a SECOND, identical pane_grid
|
||||
// layered ABOVE the crosshair overlay (`model_input_layer` below) —
|
||||
// they must sit above the selection overlay, whose `Hidden` cursor
|
||||
// interaction otherwise "levitates" the cursor and starves any layer
|
||||
// beneath it of mouse events. A separate eventless `responsive`
|
||||
// Space captures the area size to keep `vp_size` / tile rects
|
||||
// current (building the pane_grid inside `responsive` resets the
|
||||
// mouse_areas' hover state and drops their move events).
|
||||
let scene = &tab.scene;
|
||||
let show_viewcube = self.show_viewcube;
|
||||
let render_mode = tab.render_mode;
|
||||
let size_probe: Element<'_, Message> = responsive(move |size| {
|
||||
{
|
||||
let mut sel = scene.selection.borrow_mut();
|
||||
sel.vp_size = (size.width, size.height);
|
||||
}
|
||||
scene.sync_tiles_from_panes(size.width, size.height);
|
||||
Space::new().width(Fill).height(Fill).into()
|
||||
})
|
||||
.into();
|
||||
let shaders = pane_grid::PaneGrid::new(
|
||||
&scene.model_panes,
|
||||
move |_pane, &idx, _maximized| {
|
||||
pane_grid::Content::new(
|
||||
shader(ViewportPane::for_pane(
|
||||
scene,
|
||||
show_viewcube,
|
||||
render_mode,
|
||||
idx,
|
||||
))
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
},
|
||||
)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.spacing(crate::scene::TILE_DIVIDER_PX);
|
||||
stack![size_probe, shaders].width(Fill).height(Fill).into()
|
||||
};
|
||||
|
||||
// Per-pane input layer: a pane_grid of transparent mouse_areas matching
|
||||
// the shader pane_grid (same `model_panes` → identical layout). Layered
|
||||
// above the crosshair overlay so it actually receives mouse events, and
|
||||
// it owns the divider resize. Only built for the Model layout.
|
||||
let model_input_layer: Option<Element<'_, Message>> = if is_paper || tab.is_start {
|
||||
None
|
||||
} else {
|
||||
let scene = &tab.scene;
|
||||
Some(
|
||||
pane_grid::PaneGrid::new(&scene.model_panes, |_pane, &idx, _maximized| {
|
||||
pane_grid::Content::new(pane_mouse_area(idx))
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.spacing(crate::scene::TILE_DIVIDER_PX)
|
||||
.on_resize(6.0, Message::PaneResized)
|
||||
.into(),
|
||||
)
|
||||
};
|
||||
|
||||
let grid_overlay = {
|
||||
|
|
@ -211,8 +272,8 @@ impl OpenCADStudio {
|
|||
// Rotation-only projection (view_proj_rte): the icon shows axis
|
||||
// DIRECTIONS only, so the full view_proj's huge UTM translation would
|
||||
// cancel catastrophically in f32 and make the tripod jitter.
|
||||
let ucs_icon = if !self.show_ucs_icon {
|
||||
None
|
||||
let ucs_icons: Vec<crate::ui::overlay::UcsIconParams> = if !self.show_ucs_icon {
|
||||
vec![]
|
||||
} else if let Some((vp_cam, full)) = tab.scene.viewport_edit_frame((vw, vh)) {
|
||||
let (_, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
let origin_screen = self.ucs_icon_at_origin.then(|| {
|
||||
|
|
@ -220,31 +281,51 @@ impl OpenCADStudio {
|
|||
.project(tab.ucs_origin_world(), full)
|
||||
.map(|p| iced::Point::new(full.x + p.x, full.y + p.y))
|
||||
}).flatten();
|
||||
Some(crate::ui::overlay::UcsIconParams {
|
||||
vec![crate::ui::overlay::UcsIconParams {
|
||||
view_proj: vp_cam.view_proj_rte(full),
|
||||
bounds: full,
|
||||
axes: (ux, uy, uz),
|
||||
origin_screen,
|
||||
hover: self.ucs_icon_hover,
|
||||
selected: self.ucs_icon_selected,
|
||||
})
|
||||
}]
|
||||
} else if !is_paper {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
// One icon per Model pane — each at its own UCS origin, projected
|
||||
// through that pane's camera at its pane rect. Only the active
|
||||
// pane carries the interactive hover / selected grips.
|
||||
let (_, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
let origin_screen = self.ucs_icon_at_origin.then(|| {
|
||||
cam.project(tab.ucs_origin_world(), vp_bounds)
|
||||
.map(|p| iced::Point::new(vp_bounds.x + p.x, vp_bounds.y + p.y))
|
||||
}).flatten();
|
||||
Some(crate::ui::overlay::UcsIconParams {
|
||||
view_proj: cam.view_proj_rte(vp_bounds),
|
||||
bounds: vp_bounds,
|
||||
axes: (ux, uy, uz),
|
||||
origin_screen,
|
||||
hover: self.ucs_icon_hover,
|
||||
selected: self.ucs_icon_selected,
|
||||
})
|
||||
let origin_w = tab.ucs_origin_world();
|
||||
let active = tab.scene.active_model_tile.get();
|
||||
let live = tab.scene.camera.borrow().clone();
|
||||
tab.scene
|
||||
.model_tiles
|
||||
.borrow()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, t)| {
|
||||
let b = iced::Rectangle {
|
||||
x: t.rect.x * vw,
|
||||
y: t.rect.y * vh,
|
||||
width: (t.rect.width * vw).max(1.0),
|
||||
height: (t.rect.height * vh).max(1.0),
|
||||
};
|
||||
let cam = if i == active { live.clone() } else { t.camera.clone() };
|
||||
let origin_screen = self.ucs_icon_at_origin.then(|| {
|
||||
cam.project(origin_w, b)
|
||||
.map(|p| iced::Point::new(b.x + p.x, b.y + p.y))
|
||||
}).flatten();
|
||||
crate::ui::overlay::UcsIconParams {
|
||||
view_proj: cam.view_proj_rte(b),
|
||||
bounds: b,
|
||||
axes: (ux, uy, uz),
|
||||
origin_screen,
|
||||
hover: i == active && self.ucs_icon_hover,
|
||||
selected: i == active && self.ucs_icon_selected,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
None
|
||||
vec![]
|
||||
};
|
||||
|
||||
// OST tracking points → screen positions, projected relative-to-eye
|
||||
|
|
@ -272,22 +353,53 @@ impl OpenCADStudio {
|
|||
vec![]
|
||||
};
|
||||
|
||||
// Model-space tile dividers (none in paper / single-tile layouts).
|
||||
let tile_edges = if !is_paper {
|
||||
tab.scene.model_tile_edges()
|
||||
// Model-space pane dividers (none in paper / single-pane layouts).
|
||||
let dividers = if !is_paper {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
tab.scene.model_pane_dividers(vw, vh)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Pane move (drag-to-swap) visuals: source pane rect + the drop
|
||||
// target pane under the cursor.
|
||||
let (pane_move_rect, pane_drop_rect) = match self.pane_move_from {
|
||||
Some(from) if !is_paper => {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let cursor = tab.scene.selection.borrow().last_move_pos;
|
||||
let tiles = tab.scene.model_tiles.borrow();
|
||||
let px = |t: &crate::scene::ModelTile| iced::Rectangle {
|
||||
x: t.rect.x * vw,
|
||||
y: t.rect.y * vh,
|
||||
width: t.rect.width * vw,
|
||||
height: t.rect.height * vh,
|
||||
};
|
||||
let src = tiles.get(from).map(px);
|
||||
let drop = cursor.and_then(|c| {
|
||||
tiles.iter().enumerate().find(|(i, t)| {
|
||||
*i != from
|
||||
&& c.x >= t.rect.x * vw
|
||||
&& c.x < (t.rect.x + t.rect.width) * vw
|
||||
&& c.y >= t.rect.y * vh
|
||||
&& c.y < (t.rect.y + t.rect.height) * vh
|
||||
})
|
||||
});
|
||||
(src, drop.map(|(_, t)| px(t)))
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
crate::ui::overlay::selection_overlay(
|
||||
sel,
|
||||
snap_info,
|
||||
grips,
|
||||
ucs_icon,
|
||||
ucs_icons,
|
||||
ost_points,
|
||||
tab.last_cursor_screen,
|
||||
!is_paper && self.show_viewcube,
|
||||
tile_edges,
|
||||
dividers,
|
||||
pane_move_rect,
|
||||
pane_drop_rect,
|
||||
tab.pan_mode,
|
||||
)
|
||||
};
|
||||
|
|
@ -317,9 +429,10 @@ impl OpenCADStudio {
|
|||
tab.bg_color
|
||||
.map(|[r, g, b, a]| Color { r, g, b, a })
|
||||
.unwrap_or(Color {
|
||||
r: 0.11,
|
||||
g: 0.11,
|
||||
b: 0.11,
|
||||
// Default model background: RGB (33, 40, 48).
|
||||
r: 33.0 / 255.0,
|
||||
g: 40.0 / 255.0,
|
||||
b: 48.0 / 255.0,
|
||||
a: 1.0,
|
||||
})
|
||||
};
|
||||
|
|
@ -445,12 +558,18 @@ impl OpenCADStudio {
|
|||
.height(Fill),
|
||||
viewport_3d,
|
||||
selection_overlay,
|
||||
viewport_mouse,
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
};
|
||||
|
||||
// Per-pane input pane_grid goes ABOVE the crosshair overlay so it
|
||||
// receives mouse events (the overlay's `Hidden` cursor would otherwise
|
||||
// starve any layer beneath it). The controls bar is pushed on top of it.
|
||||
if let Some(input) = model_input_layer {
|
||||
viewport_stack = viewport_stack.push(input);
|
||||
}
|
||||
|
||||
// Model-space render-mode picker, top-left. Sits ABOVE the
|
||||
// viewport mouse_area so clicks inside its bounds reach it
|
||||
// instead of the shader behind it; `opaque` stops them bubbling
|
||||
|
|
@ -1675,6 +1794,23 @@ const BRAND_DARK: Color = Color {
|
|||
a: 1.0,
|
||||
};
|
||||
|
||||
/// Transparent input layer for one Model pane: a `mouse_area` filling the pane
|
||||
/// that emits pane-tagged viewport events (`idx` = the pane's tile index). The
|
||||
/// handlers offset the pane-local point to canvas coords and focus the pane.
|
||||
fn pane_mouse_area<'a>(idx: usize) -> Element<'a, Message> {
|
||||
mouse_area(container(Space::new().width(Fill).height(Fill)))
|
||||
.on_move(move |p| Message::PaneMove(idx, p))
|
||||
.on_press(Message::PanePress(idx))
|
||||
.on_release(Message::PaneRelease(idx))
|
||||
.on_right_press(Message::PaneRightPress(idx))
|
||||
.on_right_release(Message::PaneRightRelease(idx))
|
||||
.on_middle_press(Message::PaneMiddlePress(idx))
|
||||
.on_middle_release(Message::PaneMiddleRelease(idx))
|
||||
.on_scroll(move |d| Message::PaneScroll(idx, d))
|
||||
.on_exit(Message::ViewportExit)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) fn start_page_view<'a>() -> Element<'a, Message> {
|
||||
const TEXT: Color = Color {
|
||||
r: 0.94,
|
||||
|
|
|
|||
Loading…
Reference in a new issue