forked from 1b-agent/opencadstudio-upstream
fix(ui): make modal layouts intrinsic
Pin Iced 0.15 so dialogs can measure content before distributing the resolved frame across child panels. Keep the web plugin notice compact and non-resizable.\n\nCloses #582
This commit is contained in:
1 parent
284d6ef517
commit
3ab8dc3ca5
59 files changed
+2197
-1652
No files matched your search
@@ -9,7 +9,7 @@
|
||||
use crate::app::Message;
|
||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
||||
use iced::widget::{
|
||||
button, column, container, markdown, pick_list, row, rule, scrollable, text, text_input, Space,
|
||||
button, column, container, markdown, row, rule, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
@@ -48,13 +48,13 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
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.extended_palette().primary.base.color),
|
||||
color: Some(theme.palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ fn badge<'a>(label: String) -> Element<'a, Message> {
|
||||
container(text(label).size(11))
|
||||
.padding([2, 8])
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.extended_palette().primary.weak;
|
||||
let pair = theme.palette().primary.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
@@ -105,7 +105,7 @@ fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
|
||||
container(text(label.to_string()).size(11))
|
||||
.padding([2, 8])
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let palette = theme.palette();
|
||||
let pair = match kind {
|
||||
StatusKind::Muted => palette.background.weak,
|
||||
StatusKind::Success => palette.success.weak,
|
||||
@@ -126,7 +126,7 @@ fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
|
||||
}
|
||||
|
||||
fn card_style(theme: &Theme, selected: bool) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: selected
|
||||
.then(|| Background::Color(palette.primary.weak.color.scale_alpha(0.18))),
|
||||
@@ -325,7 +325,7 @@ fn install_controls<'a>(
|
||||
text("no releases").size(11).style(muted_style).into()
|
||||
} else {
|
||||
let r = repo_s.clone();
|
||||
pick_list(tags, selected, move |tag| {
|
||||
crate::ui::pick_list(tags, selected, move |tag| {
|
||||
Message::PluginReleaseSelect(r.clone(), tag)
|
||||
})
|
||||
.text_size(12)
|
||||
@@ -490,7 +490,7 @@ fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
|
||||
container(body.padding([10, 12]))
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.extended_palette().warning.weak;
|
||||
let pair = theme.palette().warning.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color.scale_alpha(0.16))),
|
||||
border: Border {
|
||||
@@ -638,7 +638,12 @@ fn resolve_readme_link(repo: &str, uri: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Message> {
|
||||
fn readme_panel<'a>(
|
||||
market: &MarketView<'a>,
|
||||
theme: &Theme,
|
||||
width: Length,
|
||||
height: Length,
|
||||
) -> Element<'a, Message> {
|
||||
let Some(repo) = market.selected_repo else {
|
||||
return container(
|
||||
column![
|
||||
@@ -649,9 +654,10 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.center(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.center_x(width)
|
||||
.center_y(height)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.style(container::bordered_box)
|
||||
.into();
|
||||
};
|
||||
@@ -667,7 +673,7 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
|
||||
text(repo.to_string()).size(11).style(primary_style),
|
||||
]
|
||||
.spacing(3)
|
||||
.width(Fill),
|
||||
.width(width),
|
||||
pill_button(
|
||||
"View on GitHub",
|
||||
Message::OpenUrl(format!("https://github.com/{repo}")),
|
||||
@@ -686,9 +692,10 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
|
||||
]
|
||||
.spacing(6),
|
||||
)
|
||||
.center(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.center_x(width)
|
||||
.center_y(height)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.into()
|
||||
} else {
|
||||
match market.readmes.get(repo) {
|
||||
@@ -715,18 +722,20 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
|
||||
]
|
||||
.spacing(6),
|
||||
)
|
||||
.center(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.center_x(width)
|
||||
.center_y(height)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.into(),
|
||||
None => container(
|
||||
text("Select the plugin again to load its README.")
|
||||
.size(12)
|
||||
.style(muted_style),
|
||||
)
|
||||
.center(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.center_x(width)
|
||||
.center_y(height)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.into(),
|
||||
}
|
||||
};
|
||||
@@ -738,18 +747,18 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
|
||||
left: 0.0,
|
||||
};
|
||||
let readme = scrollable(container(content).padding(gutter))
|
||||
.height(Fill)
|
||||
.width(Fill);
|
||||
.height(height)
|
||||
.width(width);
|
||||
|
||||
container(
|
||||
column![header, rule::horizontal(1), readme]
|
||||
.spacing(10)
|
||||
.padding([12, 14])
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
.width(width)
|
||||
.height(height),
|
||||
)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
}
|
||||
@@ -760,7 +769,10 @@ pub fn view_window<'a>(
|
||||
loaded: &FxHashSet<String>,
|
||||
market: MarketView<'a>,
|
||||
theme: &'a Theme,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let width = sizing.width;
|
||||
let height = sizing.height;
|
||||
let title = text("Plugins").size(20);
|
||||
let subtitle = text("Browse, install, and manage add-ons. Select one to view its README.")
|
||||
.size(12)
|
||||
@@ -813,40 +825,43 @@ pub fn view_window<'a>(
|
||||
left: 0.0,
|
||||
};
|
||||
let catalog = scrollable(container(list.width(Fill)).padding(gutter))
|
||||
.height(Fill)
|
||||
.height(height)
|
||||
.width(Fill);
|
||||
let search = text_input("Search plugins…", market.search)
|
||||
.on_input(Message::PluginSearchInput)
|
||||
.size(13)
|
||||
.padding([7, 10])
|
||||
.width(Fill);
|
||||
let catalog_pane = column![search, catalog].spacing(10).width(Fill).height(Fill);
|
||||
let details = readme_panel(&market, theme);
|
||||
let catalog_pane = column![search, catalog]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
.height(height);
|
||||
let details = readme_panel(&market, theme, width, height);
|
||||
let body = row![
|
||||
container(catalog_pane)
|
||||
.width(Length::Fixed(410.0))
|
||||
.height(Fill),
|
||||
.height(height),
|
||||
details,
|
||||
]
|
||||
.spacing(14)
|
||||
.height(Fill)
|
||||
.width(Fill);
|
||||
.height(height)
|
||||
.width(width);
|
||||
|
||||
container(
|
||||
column![title, subtitle, Space::new().height(12), body]
|
||||
.spacing(4)
|
||||
.padding(18)
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
.width(width)
|
||||
.height(height),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -855,28 +870,58 @@ pub fn view_window<'a>(
|
||||
pub fn view_web_notice<'a>() -> Element<'a, Message> {
|
||||
let download = button(text("Download desktop app").size(13))
|
||||
.on_press(Message::OpenUrl(DESKTOP_DOWNLOAD_URL.to_string()))
|
||||
.padding([8, 16])
|
||||
.style(button::primary);
|
||||
.padding([9, 18])
|
||||
.style(|theme: &Theme, status| {
|
||||
let mut style = button::primary(theme, status);
|
||||
style.text_color = iced::Color::WHITE;
|
||||
style
|
||||
});
|
||||
|
||||
container(
|
||||
let icon = container(crate::ui::icons::themed_primary(
|
||||
crate::ui::icons::GEAR,
|
||||
26.0,
|
||||
))
|
||||
.center(Length::Fixed(44.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().primary.weak.color,
|
||||
)),
|
||||
border: Border {
|
||||
radius: 12.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let notice = container(
|
||||
column![
|
||||
text("Plugins require the desktop app").size(20),
|
||||
icon,
|
||||
text("Plugins are available in the desktop app")
|
||||
.size(20)
|
||||
.width(Length::Fit)
|
||||
.align_x(iced::alignment::Horizontal::Center),
|
||||
text(
|
||||
"Open CAD Studio plugins are native packages and cannot run inside a browser. \
|
||||
Install the desktop app to browse, install, and use plugins.",
|
||||
Download the desktop app to browse, install, and use plugins.",
|
||||
)
|
||||
.size(13)
|
||||
.width(Length::Fit)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.style(muted_style),
|
||||
Space::new().height(8),
|
||||
Space::new().height(4),
|
||||
download,
|
||||
]
|
||||
.spacing(8)
|
||||
.padding(24)
|
||||
.width(Fill),
|
||||
.spacing(10)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.width(Length::Fit),
|
||||
)
|
||||
.center(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.width(Length::Fit.max(380.0));
|
||||
|
||||
container(notice)
|
||||
.center_x(Length::Fit)
|
||||
.padding([16, 20])
|
||||
.width(Length::Fit)
|
||||
.height(Length::Fit)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user