Merge branch 'HakanSeven12:main' into main

This commit is contained in:
dichhead authored and GitHub committed 2026-09-18 16:09:59 +02:00
commit b356baa5e5
28 files changed
+1133 -273

No files matched your search

Generated
+1
View File
@@ -45,6 +45,7 @@ dependencies = [
"objc2-foundation 0.2.2",
"ocs_plugin_api",
"open",
"pollster",
"printpdf",
"rayon",
"resvg",
+1
View File
@@ -55,6 +55,7 @@ ttf-parser = "0.25"
cosmic-text = "0.15"
unicode-bidi = "0.3"
lyon_tessellation = "1.0.20"
pollster = "0.4"
[dev-dependencies]
fluent-syntax = "0.12"
+2 -2
View File
@@ -193,7 +193,7 @@ fn call_timeout() -> Duration {
/// Per-request-kind timeout floors. The user-configured default is raised to
/// these minima so that no request kind can be configured into an unsafe value.
fn request_timeout(kind: &'static str) -> Duration {
pub(crate) fn request_timeout(kind: &'static str) -> Duration {
base_max_floor(call_timeout(), kind)
}
@@ -228,7 +228,7 @@ fn base_max_floor(base: Duration, kind: &'static str) -> Duration {
base.max(floor)
}
fn request_kind(req: &HostRequest) -> &'static str {
pub(crate) fn request_kind(req: &HostRequest) -> &'static str {
match req {
HostRequest::GetManifest => "GetManifest",
HostRequest::GetRibbon => "GetRibbon",
+1 -57
View File
@@ -17,63 +17,7 @@ use crate::ipc::v4::server::{
default_notify_rate_limit, run_host_reader_thread, HostIncoming, RateLimiter, V4HostShared,
};
use crate::process::PluginError;
/// Default maximum time to wait for a plugin call to respond.
const CALL_TIMEOUT_DEFAULT: Duration = Duration::from_secs(30);
fn call_timeout() -> Duration {
std::env::var("OCS_PLUGIN_CALL_TIMEOUT_SECS")
.ok()
.and_then(|s| s.parse().ok())
.map(Duration::from_secs)
.unwrap_or(CALL_TIMEOUT_DEFAULT)
}
/// Per-request-kind timeout floors.
fn request_timeout(kind: &'static str) -> Duration {
base_max_floor(call_timeout(), kind)
}
fn execute_code_timeout() -> Duration {
const DEFAULT: Duration = Duration::from_secs(60);
std::env::var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS")
.ok()
.and_then(|s| s.parse().ok())
.map(Duration::from_secs)
.unwrap_or(DEFAULT)
.max(DEFAULT)
}
fn base_max_floor(base: Duration, kind: &'static str) -> Duration {
#[cfg(test)]
if let Some(secs) = std::env::var("OCS_PLUGIN_TEST_FLOOR_SECS")
.ok()
.and_then(|s| s.parse().ok())
{
return base.max(Duration::from_secs(secs));
}
let floor = match kind {
"GetManifest" | "GetRibbon" => Duration::from_secs(5),
"Dispatch" => Duration::from_secs(10),
"InteractiveEvent" | "GetPrompt" | "NeedsEntityPick" => Duration::from_secs(2),
"ExecuteCode" => execute_code_timeout(),
_ => Duration::from_secs(1),
};
base.max(floor)
}
fn request_kind(req: &HostRequest) -> &'static str {
match req {
HostRequest::GetManifest => "GetManifest",
HostRequest::GetRibbon => "GetRibbon",
HostRequest::Dispatch { .. } => "Dispatch",
HostRequest::InteractiveEvent { .. } => "InteractiveEvent",
HostRequest::GetPrompt { .. } => "GetPrompt",
HostRequest::NeedsEntityPick { .. } => "NeedsEntityPick",
HostRequest::ExecuteCode { .. } => "ExecuteCode",
HostRequest::Shutdown => "Shutdown",
}
}
use crate::process::{request_kind, request_timeout};
type DeferredRequest = (u64, Option<u64>, Box<PluginRequest>);
-9
View File
@@ -196,15 +196,6 @@
<ComponentRef Id='DesktopShortcutComp' />
</Feature>
</Feature>
<CustomAction
Id='RefreshShellIcons'
Directory='APPLICATIONROOTDIRECTORY'
ExeCommand='"[SystemFolder]ie4uinit.exe" -show'
Execute='immediate'
Return='ignore' />
<InstallExecuteSequence>
<Custom Action='RefreshShellIcons' After='InstallFinalize'>NOT REMOVE</Custom>
</InstallExecuteSequence>
<UIRef Id='OpenCADStudioUI' />
</Product>
+11
View File
@@ -4393,6 +4393,17 @@ impl OpenCADStudio {
// `--read-only` disables saving. `--script` queues command lines.
let cfg = crate::cli::gui_config();
s.read_only = cfg.read_only;
// GPU backend / renderer fallback: the resolver ran before iced
// booted, so surface its verdict here where the user can see it.
if let Some(notice) = cfg.gpu_fallback_notice {
s.command_line.push_warning(&notice);
crate::scene::pipeline::report_gpu_line(&format!("[gpu] {notice}"));
}
if cfg.gpu_compat_auto {
let notice = crate::gpu_backend::compat_notice();
s.command_line.push_warning(&notice);
crate::scene::pipeline::report_gpu_line(&format!("[gpu] {notice}"));
}
let cli_open: Task<Message> = if !cfg.files.is_empty() {
Task::batch(
cfg.files
+13
View File
@@ -100,6 +100,13 @@ pub struct Cli {
#[arg(help = crate::t!("Log level (error|warn|info|debug|trace). Also reads RUST_LOG.").into_owned(), long_help = None)]
pub log: Option<String>,
/// Internal: probe one GPU backend offscreen (`dx12|vulkan|gl|sw`), print
/// one JSON line on success, then exit 0/1. Spawned by the GPU backend
/// resolver so a legacy-driver abort kills only the probe child, never
/// the editor itself.
#[arg(long, value_name = "BACKEND", hide = true)]
pub gpu_probe: Option<String>,
/// Internal: run as the plugin runner child process.
#[arg(long, value_names = ["SOCKET", "CDYLIB"], num_args = 2, hide = true)]
pub ocs_plugin_runner: Option<Vec<String>>,
@@ -124,6 +131,12 @@ pub struct GuiConfig {
pub compat_renderer: bool,
/// Command lines to run once the editor is up.
pub script_lines: Vec<String>,
/// Why this launch switched graphics backend, if it did. Shown once
/// as a command-line warning so the switch is never silent.
pub gpu_fallback_notice: Option<String>,
/// The GPU probe found no shader storage buffers: the packed
/// compatibility renderer was enabled automatically for this session.
pub gpu_compat_auto: bool,
}
/// Set once by `main` before the GUI boots; read by `app::boot`.
+1030
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -8,6 +8,8 @@ pub mod command;
pub mod config;
pub mod discussions;
pub mod entities;
#[cfg(not(target_arch = "wasm32"))]
pub mod gpu_backend;
pub mod i18n;
pub(crate) mod input;
pub mod io;
+47 -15
View File
@@ -29,6 +29,14 @@ fn main() -> iced::Result {
use clap::Parser;
let args = cli::Cli::parse();
// GPU probe child: exercise one backend offscreen, print one JSON
// line on success and exit 0/1. This must run before any logging/GUI
// setup; the parent interprets any non-zero exit (including a driver
// abort) as "this backend is unusable".
if let Some(which) = &args.gpu_probe {
OpenCADStudio::gpu_backend::run_probe_child(which);
}
// Plugin runner mode: the host spawns itself with this hidden flag to
// load a plugin cdylib in an isolated process. Hand off immediately so
// the child never touches GUI state.
@@ -77,19 +85,20 @@ fn main() -> iced::Result {
}
// GPU backend selection. Explicit `--backend` wins; `--safe-mode`
// forces GL for flaky drivers. On Windows, fall back to DX12/Vulkan so
// the AMD OpenGL ICD (atio6axx.dll) is never touched at startup — it
// access-violates on some hybrid-GPU laptops before any window appears
// (#55). An already-set WGPU_BACKEND always wins.
if let Some(backend) = &args.backend {
std::env::set_var("WGPU_BACKEND", backend);
} else if args.safe_mode {
std::env::set_var("WGPU_BACKEND", "gl");
}
#[cfg(target_os = "windows")]
if std::env::var_os("WGPU_BACKEND").is_none() {
std::env::set_var("WGPU_BACKEND", "dx12,vulkan");
}
// forces GL for flaky drivers. On Windows the preference order starts
// with DX12/Vulkan so the AMD OpenGL ICD (atio6axx.dll) is never
// touched at startup — it access-violates on some hybrid-GPU laptops
// before any window appears (#55). An already-set WGPU_BACKEND always
// wins.
//
// Older GPUs without usable DirectX 12 Feature Level 12_0 or working
// Vulkan (e.g. legacy Intel iGPUs), and old GLES drivers whose shader
// compiler rejects iced's own shaders, crash at startup or on the
// first drawing viewport instead. `resolve_gpu` probes each candidate
// backend in an isolated child process and selects the first one that
// survives a real offscreen render; a crash sentinel skips backends
// that died with the previous run.
let gpu = OpenCADStudio::gpu_backend::resolve_gpu(args.backend.as_deref(), args.safe_mode);
// Headless modes exit without ever creating a window.
if args.serve {
@@ -145,12 +154,22 @@ fn main() -> iced::Result {
}
})
.unwrap_or_default();
let gpu_fallback_notice = gpu
.reason
.map(|reason| OpenCADStudio::gpu_backend::fallback_notice(&reason));
// The probe enables the packed renderer automatically on GPUs without
// shader storage buffers; an explicit `--compat-renderer` does the
// same without the notice.
let compat_renderer = args.compat_renderer || gpu.compat_renderer;
let gpu_compat_auto = !args.compat_renderer && gpu.compat_renderer;
let _ = cli::GUI_CONFIG.set(cli::GuiConfig {
files: if args.new { Vec::new() } else { args.files },
new: args.new,
read_only: args.read_only,
compat_renderer: args.compat_renderer,
compat_renderer,
script_lines,
gpu_fallback_notice,
gpu_compat_auto,
});
// Register (or refresh) the freedesktop DWG thumbnailer so file managers
@@ -159,6 +178,19 @@ fn main() -> iced::Result {
// mode. Silently ignored on failure or non-Linux.
io::file_association::install_thumbnailer();
app::run()
// Crash sentinel: armed while the GPU is live, naming the attempted
// backend. A stale file at the next launch means this run aborted, so
// the resolver skips those backends instead of crashing again.
// Inactive on macOS/wasm, where the resolver leaves the env alone.
if OpenCADStudio::gpu_backend::gpu_guard_active() {
OpenCADStudio::gpu_backend::arm_sentinel(
gpu.backend_value.as_deref().unwrap_or("auto"),
);
let result = app::run();
OpenCADStudio::gpu_backend::disarm_sentinel();
result
} else {
app::run()
}
}
}
+1 -13
View File
@@ -6,6 +6,7 @@ use iced::widget::{
};
use iced::{mouse, Background, Border, Element, Length, Point, Rectangle, Theme};
use crate::ui::style::common::muted_style;
use crate::ui::style::form::hdivider;
use crate::t;
use std::borrow::Cow;
use std::fmt;
@@ -263,19 +264,6 @@ impl canvas::Program<Message> for DimensionPreview {
}
}
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
+6
View File
@@ -83,6 +83,12 @@ pub fn vseparator<'a>(height: Length) -> Element<'a, Message> {
.into()
}
/// Alias kept for call sites that historically used `vsep`.
/// Same implementation as [`vseparator`].
pub fn vsep<'a>(height: Length) -> Element<'a, Message> {
vseparator(height)
}
/// Muted section heading.
pub fn section_label<'a>(label: Cow<'static, str>) -> Element<'a, Message> {
text(label).size(11).style(muted_style).into()
+1 -26
View File
@@ -4,6 +4,7 @@ use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Border, Element, Theme};
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{hdivider, vsep};
use crate::t;
use std::borrow::Cow;
use std::fmt;
@@ -83,32 +84,6 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
}
}
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
fn vsep<'a>(height: iced::Length) -> Element<'a, Message> {
container(Space::new().width(1).height(height))
.width(1)
.height(height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
pub fn view_window<'a>(
document: &'a acadrust::CadDocument,
table: Option<&'a crate::io::plot_style::PlotStyleTable>,
+1 -25
View File
@@ -327,28 +327,4 @@ pub(crate) fn muted_text_style(theme: &Theme) -> iced::widget::text::Style {
crate::ui::style::common::muted_style(theme)
}
pub(crate) fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
pub(crate) fn vsep<'a>(height: iced::Length) -> Element<'a, Message> {
container(Space::new().width(1).height(height))
.width(1)
.height(height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
pub(crate) use crate::ui::style::form::{hdivider, vsep};
+1 -6
View File
@@ -1,14 +1,9 @@
use crate::app::Message;
use crate::t;
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, svg, text, Space};
use iced::{Background, Border, Element, Fill, Length, Shrink, Theme};
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().primary.base.color),
+1 -6
View File
@@ -7,6 +7,7 @@
//! unsaved-changes guard on close.
use crate::app::Message;
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Element, Length, Theme};
use crate::t;
@@ -21,12 +22,6 @@ pub enum AliasField {
/// Right-hand lane reserved for the scrollbar so it never overlaps the ✕ column.
const GUTTER: f32 = 16.0;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn danger_text_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().danger.base.color),
+2 -20
View File
@@ -25,6 +25,8 @@ use std::borrow::Cow;
use std::fmt;
use crate::ui::properties::{lw_options, LwItem};
use crate::ui::style::common::muted_style;
use crate::ui::style::form::hdivider;
const LABEL_W: f32 = 120.0;
@@ -139,12 +141,6 @@ pub fn color_from_label(label: &str) -> Option<AcadColor> {
})
}
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
let palette = theme.palette();
let border = match status {
@@ -161,20 +157,6 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
}
}
/// Horizontal 1px divider in the shared border colour (matches style windows).
fn hdivider<'a>(width: Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
/// One `label : widget` row with a fixed-width label column.
fn field_row<'a>(
label: Cow<'static, str>,
+1 -6
View File
@@ -2,6 +2,7 @@
use crate::app::Message;
use crate::ui::properties::{lw_options, LinetypeItem, LwItem};
use crate::ui::style::common::muted_style;
use crate::ui::ROW_H;
use acadrust::tables::layer::Layer as DocLayer;
use acadrust::tables::Table;
@@ -51,12 +52,6 @@ const LINETYPE_MENU_W: f32 = 220.0;
/// scrolled into view after it is added (#271).
pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll";
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn table_input_style(
theme: &Theme,
status: iced::widget::text_input::Status,
+2 -32
View File
@@ -1,16 +1,12 @@
//! Layout Manager window — fills the entire OS window.
use crate::app::Message;
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{hdivider, vsep};
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Element, Theme};
use crate::t;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().primary.base.color),
@@ -37,32 +33,6 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
}
}
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color,
)),
..Default::default()
})
.into()
}
fn vsep<'a>(height: iced::Length) -> Element<'a, Message> {
container(Space::new().width(1).height(height))
.width(1)
.height(height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color,
)),
..Default::default()
})
.into()
}
pub fn view_window<'a>(
layouts: Vec<String>,
selected: &'a str,
+1 -6
View File
@@ -17,6 +17,7 @@ use crate::app::Message;
use crate::scene::named_parameters::ParameterTable;
use crate::scene::Scene;
use crate::t;
use crate::ui::style::common::muted_style;
use acadrust::types::Handle;
use acadrust::EntityType;
use iced::widget::tooltip::Position as TipPos;
@@ -113,12 +114,6 @@ pub struct ParamEditorRow {
/// column — same convention and value as `alias_editor::GUTTER`.
const GUTTER: f32 = 16.0;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn danger_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().danger.base.color),
+1 -8
View File
@@ -9,6 +9,7 @@ use crate::io::paper_catalog::{self, CustomPaper, Margins, PaperSize, PaperUnits
use crate::io::plot_device::PrinterCapabilities;
use crate::ui::style::common::muted_style;
use crate::ui::style::form;
use crate::ui::style::form::{hdivider, vseparator as vsep};
use iced::widget::{
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input,
Space,
@@ -543,18 +544,10 @@ fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
form::button_style(accent)
}
fn hdivider<'a>(width: Length) -> Element<'a, Message> {
form::hdivider(width)
}
fn section_label<'a>(s: Cow<'static, str>) -> Element<'a, Message> {
form::section_label(s)
}
fn vsep<'a>(height: Length) -> Element<'a, Message> {
form::vseparator(height)
}
fn setup_row<'a>(
name: &'a str,
selected: &str,
+1 -6
View File
@@ -8,6 +8,7 @@
use crate::app::Message;
use crate::plugin::external::{ExternalPlugin, RegistryEntry, ReleaseInfo};
use crate::ui::style::common::muted_style;
use iced::widget::{
button, column, container, markdown, row, rule, scrollable, text, text_input, Space,
};
@@ -48,12 +49,6 @@ inventory::submit!(crate::command::CommandRegistration {
names: &["PLUGINS", "PLUGINMANAGER"]
});
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().primary.base.color),
+1 -6
View File
@@ -1,5 +1,6 @@
use crate::app::Message;
use crate::t;
use crate::ui::style::common::muted_style as muted;
use iced::widget::{button, checkbox, column, container, row, scrollable, text, Space};
use iced::{Background, Border, Element, Length, Theme};
@@ -13,12 +14,6 @@ fn button_style(primary: bool) -> impl Fn(&Theme, button::Status) -> button::Sty
}
}
fn muted(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
pub fn view_window<'a>(
layouts: &'a [(String, bool)],
printer: Option<&'a str>,
+1 -6
View File
@@ -1,14 +1,9 @@
use crate::app::Message;
use crate::io::recovery::{RecoveryReport, RecoveryStatus};
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, scrollable, text, Space};
use iced::{Background, Border, Element, Fill, Length, Shrink, Theme};
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn status_style(
status: RecoveryStatus,
) -> impl Fn(&Theme) -> iced::widget::text::Style + Copy {
+1 -6
View File
@@ -2,6 +2,7 @@
use crate::app::Message;
use crate::t;
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Element, Length, Theme};
@@ -13,12 +14,6 @@ pub enum ShortcutField {
const GUTTER: f32 = 16.0;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn danger_text_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().danger.base.color),
+1 -6
View File
@@ -1,15 +1,10 @@
use crate::app::Message;
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, scrollable, text, Space};
use iced::{Background, Border, Element, Theme};
use crate::t;
use std::borrow::Cow;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().primary.base.color),
+1 -6
View File
@@ -5,6 +5,7 @@
//! unreadable sliver.
use crate::app::Message;
use crate::ui::style::common::muted_style;
use iced::widget::{button, column, container, row, text, Space};
use iced::{Background, Element, Fill, Length, Theme};
@@ -12,12 +13,6 @@ use iced::{Background, Element, Fill, Length, Theme};
/// missing or broken behavior where it will be seen.
pub const XREF_ISSUES_URL: &str = "https://github.com/HakanSeven12/OpenCADStudio/issues";
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
pub fn view_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Message> {
// Fixed window width: paragraphs wrap against this instead of the
// toolbar, which is what made the dropdown unreadable.
+1 -6
View File
@@ -9,6 +9,7 @@ use crate::app::Message;
use crate::io::xref::collect_entries_with_prev;
use crate::io::xref_model::{normalize_lexical, Pathtype, RefKind, RefStatus, RefType, ReferenceEntry};
use crate::ui::ROW_H;
use crate::ui::style::common::muted_style;
use acadrust::CadDocument;
use iced::widget::{button, column, container, mouse_area, row, scrollable, text, tooltip};
use iced::Padding;
@@ -1286,12 +1287,6 @@ fn format_date(modified: Option<SystemTime>) -> String {
// ── Widget helpers (layers.rs conventions) ────────────────────────────────
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn row_button_style(selected: bool, index: usize) -> impl Fn(&Theme, button::Status) -> button::Style {
move |theme: &Theme, status: button::Status| {
let palette = theme.palette();