forked from 1b-agent/opencadstudio-upstream
Reduce the ViewCube 30% (VIEWCUBE_PX 120→84) and tighten the nav region (NAV_INSET_F 2.2→2.0) so the home / roll / nudge controls hug the cube in the corner instead of floating in a large dead area, with sizes scaled to keep them from overlapping. Bump the home and roll buttons back up slightly, shift the UCS picker down below the region, and give it a fixed width so it centres exactly under the cube centre. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
202 lines
6.5 KiB
Rust
202 lines
6.5 KiB
Rust
use super::super::Message;
|
|
use crate::scene::{VIEWCUBE_PX, VIEWCUBE_REGION_PX};
|
|
use iced::widget::{
|
|
button, column, container, mouse_area, pick_list, row, stack,
|
|
Space,
|
|
};
|
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
|
|
|
// ── Render-mode picker ──────────────────────────────────────────────────────
|
|
|
|
/// Top-left viewport control bar: a single dark chip holding (optionally) the
|
|
/// horizontal/vertical split buttons, the render-mode picker, and the grid /
|
|
/// grid-snap toggles. `include_split` is off for paper-space viewports, which
|
|
/// have no model-tile splitting. Grid / snap reflect the active viewport's
|
|
/// state and emit `ToggleGrid` / `ToggleGridSnap`.
|
|
// ── ViewCube navigation controls (home / roll / nudge / UCS) ───────────────
|
|
|
|
/// Place `el` at pixel offset (x, y) inside a Fill layer (top-left origin).
|
|
fn vc_place<'a>(x: f32, y: f32, el: Element<'a, Message>) -> Element<'a, Message> {
|
|
column![
|
|
Space::new().height(iced::Length::Fixed(y.max(0.0))),
|
|
row![Space::new().width(iced::Length::Fixed(x.max(0.0))), el],
|
|
]
|
|
.width(Fill)
|
|
.height(Fill)
|
|
.into()
|
|
}
|
|
|
|
/// Borderless square icon button used by the ViewCube nav controls.
|
|
fn vc_btn<'a>(content: Element<'a, Message>, size: f32, msg: Message) -> Element<'a, Message> {
|
|
button(
|
|
container(content)
|
|
.width(iced::Length::Fixed(size))
|
|
.height(iced::Length::Fixed(size))
|
|
.center_x(iced::Length::Fixed(size))
|
|
.center_y(iced::Length::Fixed(size)),
|
|
)
|
|
.padding(0)
|
|
.on_press(msg)
|
|
.style(|_: &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.45,
|
|
g: 0.62,
|
|
b: 0.95,
|
|
a: 0.30,
|
|
},
|
|
_ => Color::TRANSPARENT,
|
|
})),
|
|
border: Border {
|
|
radius: 3.0.into(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
})
|
|
.into()
|
|
}
|
|
|
|
/// Overlay of home / roll / nudge controls sized to the whole nav region, so
|
|
/// the caller can position it exactly like the cube hit area.
|
|
pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
|
use crate::scene::NudgeDir;
|
|
use crate::ui::icons;
|
|
let tint = Color {
|
|
r: 0.86,
|
|
g: 0.89,
|
|
b: 0.96,
|
|
a: 1.0,
|
|
};
|
|
let r = VIEWCUBE_REGION_PX;
|
|
let c = r * 0.5;
|
|
let cube_half = VIEWCUBE_PX as f32 * 0.36; // VIEWCUBE_PX * VIEWCUBE_SCALE
|
|
let nr = cube_half + 6.0; // nudge triangle distance from centre
|
|
const BTN: f32 = 16.0;
|
|
const TRI: f32 = 9.0;
|
|
let ctr = |cx: f32, cy: f32, s: f32| (cx - s * 0.5, cy - s * 0.5);
|
|
|
|
// Home top-left, roll arrows top-right.
|
|
let (rax, ray) = (r - 2.0 * BTN - 4.0, 2.0);
|
|
let (rbx, rby) = (r - BTN - 2.0, 2.0);
|
|
// Nudge triangles pointing inward at the four cube faces.
|
|
let (tux, tuy) = ctr(c, c - nr, TRI);
|
|
let (tdx, tdy) = ctr(c, c + nr, TRI);
|
|
let (tlx, tly) = ctr(c - nr, c, TRI);
|
|
let (trx, try_) = ctr(c + nr, c, TRI);
|
|
|
|
// Bottom layer: the cube/cardinal hit area covering the whole region. The
|
|
// control buttons sit ABOVE it in the same stack, so a click on a button is
|
|
// caught by the button while a click on the cube (or empty space) falls
|
|
// through to this mouse_area → ViewportClick. Moves keep cursor_pos current.
|
|
let cube_hit = mouse_area(
|
|
Space::new()
|
|
.width(iced::Length::Fixed(r))
|
|
.height(iced::Length::Fixed(r)),
|
|
)
|
|
.on_move(Message::CursorMoved)
|
|
.on_press(Message::ViewportClick);
|
|
|
|
let controls = stack![
|
|
cube_hit,
|
|
vc_place(
|
|
3.0,
|
|
3.0,
|
|
vc_btn(icons::home(13.0, tint), BTN, Message::ViewCubeHome)
|
|
),
|
|
vc_place(
|
|
rax,
|
|
ray,
|
|
vc_btn(icons::undo(12.0, tint), BTN, Message::ViewCubeRoll(false))
|
|
),
|
|
vc_place(
|
|
rbx,
|
|
rby,
|
|
vc_btn(icons::redo(12.0, tint), BTN, Message::ViewCubeRoll(true))
|
|
),
|
|
vc_place(
|
|
tux,
|
|
tuy,
|
|
vc_btn(
|
|
icons::arrow_down(8.0, tint),
|
|
TRI,
|
|
Message::ViewCubeNudge(NudgeDir::Up)
|
|
)
|
|
),
|
|
vc_place(
|
|
tdx,
|
|
tdy,
|
|
vc_btn(
|
|
icons::arrow_up(8.0, tint),
|
|
TRI,
|
|
Message::ViewCubeNudge(NudgeDir::Down)
|
|
)
|
|
),
|
|
vc_place(
|
|
tlx,
|
|
tly,
|
|
vc_btn(
|
|
icons::arrow_right(8.0, tint),
|
|
TRI,
|
|
Message::ViewCubeNudge(NudgeDir::Left)
|
|
)
|
|
),
|
|
vc_place(
|
|
trx,
|
|
try_,
|
|
vc_btn(
|
|
icons::arrow_left(8.0, tint),
|
|
TRI,
|
|
Message::ViewCubeNudge(NudgeDir::Right)
|
|
)
|
|
),
|
|
];
|
|
|
|
container(controls)
|
|
.width(iced::Length::Fixed(r))
|
|
.height(iced::Length::Fixed(r))
|
|
.into()
|
|
}
|
|
|
|
/// Fixed width of the UCS picker, so it can be centred under the cube.
|
|
pub(super) const UCS_PICKER_W: f32 = 84.0;
|
|
|
|
/// The WCS / named-UCS selector shown under the cube.
|
|
pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> Element<'a, Message> {
|
|
let light = Color {
|
|
r: 0.85,
|
|
g: 0.87,
|
|
b: 0.93,
|
|
a: 1.0,
|
|
};
|
|
let mut options = vec!["WCS".to_string()];
|
|
options.extend(names);
|
|
let selected = if current.is_empty() {
|
|
"WCS".to_string()
|
|
} else {
|
|
current
|
|
};
|
|
pick_list(options, Some(selected), Message::SetViewcubeUcs)
|
|
.text_size(11)
|
|
.padding([2, 6])
|
|
// Fixed width so the caller can centre it under the cube centre with a
|
|
// simple half-width offset (content-sized width would drift off-centre).
|
|
.width(iced::Length::Fixed(UCS_PICKER_W))
|
|
.style(move |_: &Theme, _| iced::widget::pick_list::Style {
|
|
background: Background::Color(Color {
|
|
r: 0.16,
|
|
g: 0.17,
|
|
b: 0.20,
|
|
a: 0.92,
|
|
}),
|
|
border: Border {
|
|
radius: 3.0.into(),
|
|
..Default::default()
|
|
},
|
|
text_color: light,
|
|
placeholder_color: light,
|
|
handle_color: light,
|
|
})
|
|
.into()
|
|
}
|
|
|