Modes menu group and submenu items:
Free (prettyu much implemented) Relative (also pretty close) Anchored (will take more than the afformentioned two, more to come)
This commit is contained in:
parent
32c66cd330
commit
62f5d6212e
|
|
@ -171,6 +171,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
mainMenu.addItem(buildFileMenu())
|
||||
mainMenu.addItem(buildEditMenu())
|
||||
mainMenu.addItem(buildRenderMenu())
|
||||
mainMenu.addItem(buildModeMenu())
|
||||
mainMenu.addItem(buildViewMenu())
|
||||
mainMenu.addItem(buildWindowMenu())
|
||||
|
||||
|
|
@ -345,6 +346,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
return item
|
||||
}
|
||||
|
||||
private func buildModeMenu() -> NSMenuItem {
|
||||
let item = NSMenuItem()
|
||||
let menu = NSMenu(title: "Mode")
|
||||
|
||||
let freeItem = NSMenuItem(title: "Free", action: #selector(setLayoutModeFree), keyEquivalent: "")
|
||||
freeItem.target = self
|
||||
menu.addItem(freeItem)
|
||||
|
||||
let relativeItem = NSMenuItem(title: "Relative", action: #selector(setLayoutModeRelative), keyEquivalent: "")
|
||||
relativeItem.target = self
|
||||
menu.addItem(relativeItem)
|
||||
|
||||
let anchoredItem = NSMenuItem(title: "Anchored", action: #selector(setLayoutModeAnchored), keyEquivalent: "")
|
||||
anchoredItem.target = self
|
||||
menu.addItem(anchoredItem)
|
||||
|
||||
menu.addItem(.separator())
|
||||
|
||||
let snappingItem = NSMenuItem(title: "Snapping", action: #selector(toggleSnapping), keyEquivalent: "")
|
||||
snappingItem.target = self
|
||||
menu.addItem(snappingItem)
|
||||
|
||||
item.submenu = menu
|
||||
return item
|
||||
}
|
||||
|
||||
private func buildViewMenu() -> NSMenuItem {
|
||||
let item = NSMenuItem()
|
||||
let menu = NSMenu(title: "View")
|
||||
|
|
@ -647,6 +674,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
|
||||
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
||||
let mode = viewport?.renderMode() ?? 0
|
||||
let layout = viewport?.layoutMode() ?? 0
|
||||
switch menuItem.action {
|
||||
case #selector(setLiveMode):
|
||||
menuItem.state = mode == 0 ? .on : .off
|
||||
|
|
@ -654,6 +682,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
menuItem.state = mode == 1 ? .on : .off
|
||||
case #selector(setViewMode):
|
||||
menuItem.state = mode == 2 ? .on : .off
|
||||
case #selector(setLayoutModeFree):
|
||||
menuItem.state = layout == 0 ? .on : .off
|
||||
case #selector(setLayoutModeRelative):
|
||||
menuItem.state = layout == 1 ? .on : .off
|
||||
case #selector(setLayoutModeAnchored):
|
||||
menuItem.state = layout == 2 ? .on : .off
|
||||
case #selector(toggleSnapping):
|
||||
menuItem.state = (viewport?.snapping() ?? false) ? .on : .off
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -676,6 +712,22 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
viewport?.sendCommand(13)
|
||||
}
|
||||
|
||||
@objc private func setLayoutModeFree() {
|
||||
viewport?.sendCommand(17)
|
||||
}
|
||||
|
||||
@objc private func setLayoutModeRelative() {
|
||||
viewport?.sendCommand(18)
|
||||
}
|
||||
|
||||
@objc private func setLayoutModeAnchored() {
|
||||
viewport?.sendCommand(19)
|
||||
}
|
||||
|
||||
@objc private func toggleSnapping() {
|
||||
viewport?.sendCommand(20)
|
||||
}
|
||||
|
||||
@objc private func formatDocument() {
|
||||
viewport?.sendCommand(10)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,6 +298,17 @@ class IcedViewportView: NSView {
|
|||
return viewport_render_mode(h)
|
||||
}
|
||||
|
||||
/// Returns 0 = Free, 1 = Relative, 2 = Anchored.
|
||||
func layoutMode() -> UInt32 {
|
||||
guard let h = viewportHandle else { return 0 }
|
||||
return viewport_layout_mode(h)
|
||||
}
|
||||
|
||||
func snapping() -> Bool {
|
||||
guard let h = viewportHandle else { return false }
|
||||
return viewport_snapping(h)
|
||||
}
|
||||
|
||||
func setSettingsView(themeMode: String, lineIndicator: String, gutterRainbow: Bool, autoSaveDir: String) {
|
||||
guard let h = viewportHandle else { return }
|
||||
themeMode.withCString { t in
|
||||
|
|
|
|||
|
|
@ -173,4 +173,14 @@ void browser_send_command(struct BrowserHandle *handle, uint32_t command);
|
|||
|
||||
uint32_t viewport_render_mode(struct ViewportHandle *handle);
|
||||
|
||||
/**
|
||||
* returns 0 for free, 1 for relative, 2 for anchored layout mode.
|
||||
*/
|
||||
uint32_t viewport_layout_mode(struct ViewportHandle *handle);
|
||||
|
||||
/**
|
||||
* returns true when 0.25-line snapping is on for drags and resizes.
|
||||
*/
|
||||
bool viewport_snapping(struct ViewportHandle *handle);
|
||||
|
||||
#endif /* ACORD_VIEWPORT_H */
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ pub enum RenderMode {
|
|||
View,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LayoutMode {
|
||||
/// objects float on layers above zero with absolute coordinates
|
||||
Free,
|
||||
/// objects stay on layer 0 and reorder by drag
|
||||
Relative,
|
||||
/// objects sit at fixed positions and layer 0 wraps around the cutouts
|
||||
Anchored,
|
||||
}
|
||||
|
||||
/// gutter line-number and cursorline display mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LineIndicator {
|
||||
|
|
@ -183,6 +193,10 @@ pub enum Message {
|
|||
ResizePress { node_id: FreeNodeId, horiz: bool, vert: bool },
|
||||
/// mouse released after a resize press.
|
||||
ResizeRelease,
|
||||
/// switches between free, relative, and anchored layout modes.
|
||||
SetLayoutMode(LayoutMode),
|
||||
/// toggles 0.25-line snap on drags and resizes.
|
||||
ToggleSnapping,
|
||||
ToggleMenu(MenuCategory),
|
||||
CloseMenu,
|
||||
Shell(ShellAction),
|
||||
|
|
@ -193,6 +207,7 @@ pub enum MenuCategory {
|
|||
File,
|
||||
Edit,
|
||||
Render,
|
||||
Mode,
|
||||
View,
|
||||
}
|
||||
|
||||
|
|
@ -214,10 +229,11 @@ pub enum ShellAction {
|
|||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
const MENU_CATS: [(MenuCategory, &'static str); 4] = [
|
||||
const MENU_CATS: [(MenuCategory, &'static str); 5] = [
|
||||
(MenuCategory::File, "File"),
|
||||
(MenuCategory::Edit, "Edit"),
|
||||
(MenuCategory::Render, "Render"),
|
||||
(MenuCategory::Mode, "Mode"),
|
||||
(MenuCategory::View, "View"),
|
||||
];
|
||||
|
||||
|
|
@ -497,6 +513,8 @@ pub struct EditorState {
|
|||
pub promote_snapshot_pushed: bool,
|
||||
pub resize_drag: Option<ResizeDragState>,
|
||||
pub active_free: Option<FreeNodeId>,
|
||||
pub layout_mode: LayoutMode,
|
||||
pub snapping: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -620,9 +638,19 @@ impl EditorState {
|
|||
promote_snapshot_pushed: false,
|
||||
resize_drag: None,
|
||||
active_free: None,
|
||||
layout_mode: LayoutMode::Free,
|
||||
snapping: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// rounds a coordinate to the nearest 0.25-line increment.
|
||||
fn snap_to_grid(&self, value: f32) -> f32 {
|
||||
if !self.snapping { return value; }
|
||||
let unit = self.font_size * 0.325;
|
||||
if unit <= 0.0 { return value; }
|
||||
(value / unit).round() * unit
|
||||
}
|
||||
|
||||
/// applies the current cursor delta to any active promote drag.
|
||||
pub fn tick_promote_drag(&mut self) -> bool {
|
||||
let (node_id, layer, origin, size, dx, dy, was_escalated) = {
|
||||
|
|
@ -644,8 +672,8 @@ impl EditorState {
|
|||
if let Some(pd) = self.promote_drag.as_mut() { pd.escalated = true; }
|
||||
let placement = FreePlacement {
|
||||
layer,
|
||||
x: origin.0 + dx,
|
||||
y: origin.1 + dy,
|
||||
x: self.snap_to_grid(origin.0 + dx),
|
||||
y: self.snap_to_grid(origin.1 + dy),
|
||||
w: size.0,
|
||||
h: size.1,
|
||||
};
|
||||
|
|
@ -664,8 +692,8 @@ impl EditorState {
|
|||
let dy = self.cursor_pos.y - rd.start_cursor.y;
|
||||
(rd.node_id.clone(), dx, dy, rd.start_size, rd.axes, rd.snapshot_pushed)
|
||||
};
|
||||
let new_w = if axes.0 { (start_size.0 + dx).max(60.0) } else { start_size.0 };
|
||||
let new_h = if axes.1 { (start_size.1 + dy).max(40.0) } else { start_size.1 };
|
||||
let new_w = if axes.0 { self.snap_to_grid((start_size.0 + dx).max(60.0)) } else { start_size.0 };
|
||||
let new_h = if axes.1 { self.snap_to_grid((start_size.1 + dy).max(40.0)) } else { start_size.1 };
|
||||
let changed = self
|
||||
.free_placements
|
||||
.get(&node_id)
|
||||
|
|
@ -752,6 +780,7 @@ impl EditorState {
|
|||
/// arms a drag promotion for any free-layer node in live mode.
|
||||
fn start_promote(&mut self, node_id: FreeNodeId, fallback_table_idx: Option<usize>) {
|
||||
if !matches!(self.render_mode, RenderMode::Live) { return; }
|
||||
if !matches!(self.layout_mode, LayoutMode::Free) { return; }
|
||||
let existing = self.free_placements.get(&node_id).copied();
|
||||
let (origin, size, layer) = match existing {
|
||||
Some(p) => ((p.x, p.y), (p.w, p.h), p.layer),
|
||||
|
|
@ -3590,6 +3619,13 @@ impl EditorState {
|
|||
Message::ResizeRelease => {
|
||||
self.resize_drag = None;
|
||||
}
|
||||
Message::SetLayoutMode(mode) => {
|
||||
self.layout_mode = mode;
|
||||
self.snapping = !matches!(mode, LayoutMode::Free);
|
||||
}
|
||||
Message::ToggleSnapping => {
|
||||
self.snapping = !self.snapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4807,6 +4843,13 @@ impl EditorState {
|
|||
sep(),
|
||||
item("Evaluate", "Ctrl+E", Message::SmartEval),
|
||||
],
|
||||
MenuCategory::Mode => vec![
|
||||
item("Free", if matches!(self.layout_mode, LayoutMode::Free) { "•" } else { "" }, Message::SetLayoutMode(LayoutMode::Free)),
|
||||
item("Relative", if matches!(self.layout_mode, LayoutMode::Relative) { "•" } else { "" }, Message::SetLayoutMode(LayoutMode::Relative)),
|
||||
item("Anchored", if matches!(self.layout_mode, LayoutMode::Anchored) { "•" } else { "" }, Message::SetLayoutMode(LayoutMode::Anchored)),
|
||||
sep(),
|
||||
item("Snapping", if self.snapping { "✓" } else { "" }, Message::ToggleSnapping),
|
||||
],
|
||||
MenuCategory::View => vec![
|
||||
item("Zoom In", "Ctrl+=", Message::ZoomIn),
|
||||
item("Zoom Out", "Ctrl+-", Message::ZoomOut),
|
||||
|
|
@ -4825,6 +4868,7 @@ impl EditorState {
|
|||
MenuCategory::File => "Export as Rust Library".len(),
|
||||
MenuCategory::Edit => "Insert Table".len(),
|
||||
MenuCategory::Render => "Evaluate".len(),
|
||||
MenuCategory::Mode => "Anchored".len(),
|
||||
MenuCategory::View => "Reset Zoom".len(),
|
||||
};
|
||||
let max_hint_chars = 13_usize; // widest hint string in chars
|
||||
|
|
|
|||
|
|
@ -450,6 +450,10 @@ pub extern "C" fn viewport_send_command(handle: *mut ViewportHandle, command: u3
|
|||
12 => h.state.update(editor::Message::SetRenderMode(editor::RenderMode::Editor)),
|
||||
13 => h.state.update(editor::Message::SetRenderMode(editor::RenderMode::View)),
|
||||
16 => h.state.settings_open = !h.state.settings_open,
|
||||
17 => h.state.update(editor::Message::SetLayoutMode(editor::LayoutMode::Free)),
|
||||
18 => h.state.update(editor::Message::SetLayoutMode(editor::LayoutMode::Relative)),
|
||||
19 => h.state.update(editor::Message::SetLayoutMode(editor::LayoutMode::Anchored)),
|
||||
20 => h.state.update(editor::Message::ToggleSnapping),
|
||||
_ => return,
|
||||
};
|
||||
h.needs_redraw = true;
|
||||
|
|
@ -660,3 +664,27 @@ pub extern "C" fn viewport_render_mode(handle: *mut ViewportHandle) -> u32 {
|
|||
editor::RenderMode::View => 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns 0 for free, 1 for relative, 2 for anchored layout mode.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn viewport_layout_mode(handle: *mut ViewportHandle) -> u32 {
|
||||
let h = match unsafe { handle.as_mut() } {
|
||||
Some(h) => h,
|
||||
None => return 0,
|
||||
};
|
||||
match h.state.layout_mode {
|
||||
editor::LayoutMode::Free => 0,
|
||||
editor::LayoutMode::Relative => 1,
|
||||
editor::LayoutMode::Anchored => 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns true when 0.25-line snapping is on for drags and resizes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn viewport_snapping(handle: *mut ViewportHandle) -> bool {
|
||||
let h = match unsafe { handle.as_mut() } {
|
||||
Some(h) => h,
|
||||
None => return false,
|
||||
};
|
||||
h.state.snapping
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue