mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Build radio widget entries from choice type metadata so variants display their icons (#4408)
* Build radio widget entries from choice type metadata so variants display their icons * Route the node properties enum radio through the shared choice type entry builder
This commit is contained in:
committed by
Dennis Kobert
parent
8a93b47788
commit
93a1b1f456
@@ -1,2 +1,2 @@
|
||||
https://github.com/Keavon/graphite-branded-assets/archive/0d004aa61e6b48d316e8e5db6d59ccc4788f192d.tar.gz
|
||||
772d64518be43c99977ba56f69e574531c56e83d2df2f42ab066f77f74b0dd1f
|
||||
https://github.com/Keavon/graphite-branded-assets/archive/6687dc6d3d8552948458d00d35e2bee3a40ab66b.tar.gz
|
||||
7663b4a35f377a5d3363430a725a1d5f634fbdc56adca5e43167786b8fe1b253
|
||||
|
||||
@@ -623,14 +623,7 @@ impl ColorPickerMessageHandler {
|
||||
|
||||
// Gradient spread (only present when the picker is in gradient mode)
|
||||
if self.gradient.is_some() {
|
||||
let entries = [GradientSpread::Pad, GradientSpread::Reflect, GradientSpread::Repeat]
|
||||
.into_iter()
|
||||
.map(|gradient_spread| {
|
||||
RadioEntryData::new(format!("{gradient_spread:?}"))
|
||||
.label(gradient_spread.to_string())
|
||||
.on_update(move |_| ColorPickerMessage::SetGradientSpread { gradient_spread }.into())
|
||||
})
|
||||
.collect();
|
||||
let entries = RadioEntryData::list_from_choice_type(|gradient_spread| ColorPickerMessage::SetGradientSpread { gradient_spread }.into());
|
||||
|
||||
groups.push(LayoutGroup::row(vec![
|
||||
TextLabel::new("Ends").tooltip_label("Gradient Spread").tooltip_description(ENDS_DESCRIPTION).widget_instance(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::messages::frontend::IconName;
|
||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
use derivative::*;
|
||||
use graphene_std::Color;
|
||||
@@ -344,6 +345,30 @@ pub struct RadioEntryData {
|
||||
pub on_commit: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
impl RadioEntryData {
|
||||
/// One entry per variant of a choice type enum, shown as the variant's icon when it has one and its text label otherwise.
|
||||
pub fn list_from_choice_type<E>(to_message: impl Fn(E) -> Message + Clone + Send + Sync + 'static) -> Vec<Self>
|
||||
where
|
||||
E: graphene_std::choice_type::ChoiceTypeStatic + 'static,
|
||||
{
|
||||
E::list()
|
||||
.iter()
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(variant, metadata)| {
|
||||
let to_message = to_message.clone();
|
||||
let variant = *variant;
|
||||
|
||||
let entry = RadioEntryData::new(metadata.name)
|
||||
.tooltip_label(metadata.label)
|
||||
.tooltip_description(metadata.description.unwrap_or_default())
|
||||
.on_update(move |_| to_message(variant));
|
||||
|
||||
if let Some(icon) = metadata.icon { entry.icon(icon) } else { entry.label(metadata.label) }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
|
||||
@@ -2631,7 +2631,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
.iter()
|
||||
.map(|&gradient_form| {
|
||||
RadioEntryData::new(format!("{:?}", gradient_form))
|
||||
.label(format!("{:?}", gradient_form))
|
||||
.label(gradient_form.to_string())
|
||||
.on_update(update_value(move |_| TaggedValue::GradientForm(gradient_form), node_id, GradientFormInput))
|
||||
.on_commit(commit_value)
|
||||
})
|
||||
@@ -2933,20 +2933,18 @@ pub mod choice {
|
||||
U: Fn(&E) -> Message + 'static + Send + Sync,
|
||||
C: Fn(&()) -> Message + 'static + Send + Sync,
|
||||
{
|
||||
let items = E::list()
|
||||
.iter()
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(item, var_meta)| {
|
||||
let updater = updater_factory();
|
||||
let committer = committer_factory();
|
||||
let entry = RadioEntryData::new(var_meta.name)
|
||||
.on_update(move |_| updater(item))
|
||||
.on_commit(committer)
|
||||
.tooltip_label(var_meta.label)
|
||||
.tooltip_description(var_meta.description.unwrap_or_default());
|
||||
if let Some(icon) = var_meta.icon { entry.icon(icon) } else { entry.label(var_meta.label) }
|
||||
// The entry builder clones one callback across all variants, so each factory yields a single shared handle
|
||||
let updater = std::sync::Arc::new(updater_factory());
|
||||
let committer = std::sync::Arc::new(committer_factory());
|
||||
|
||||
let items = RadioEntryData::list_from_choice_type(move |variant: E| updater(&variant))
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let committer = committer.clone();
|
||||
entry.on_commit(move |value| committer(value))
|
||||
})
|
||||
.collect();
|
||||
|
||||
RadioInput::new(items).selected_index(Some(current.as_u32())).disabled(self.disabled).widget_instance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,19 +123,9 @@ where
|
||||
E: ChoiceTypeStatic + 'static,
|
||||
F: Fn(E) -> Message + 'static + Send + Sync + Clone,
|
||||
{
|
||||
let entries = E::list()
|
||||
.iter()
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(value, meta)| {
|
||||
let to_message = to_message.clone();
|
||||
let value = *value;
|
||||
let entry = RadioEntryData::new(meta.name)
|
||||
.tooltip_label(meta.label)
|
||||
.tooltip_description(meta.description.unwrap_or_default())
|
||||
.on_update(move |_| to_message(value))
|
||||
.on_commit(|_| DocumentMessage::StartTransaction.into());
|
||||
if let Some(icon) = meta.icon { entry.icon(icon) } else { entry.label(meta.label) }
|
||||
})
|
||||
let entries = RadioEntryData::list_from_choice_type(to_message)
|
||||
.into_iter()
|
||||
.map(|entry| entry.on_commit(|_| DocumentMessage::StartTransaction.into()))
|
||||
.collect();
|
||||
vec![
|
||||
TextLabel::new(label_text).table_align(true).widget_instance(),
|
||||
|
||||
@@ -231,22 +231,13 @@ impl LayoutHolder for GradientTool {
|
||||
fn layout(&self) -> Layout {
|
||||
let mut widgets: Vec<WidgetInstance> = Vec::new();
|
||||
|
||||
let gradient_form = RadioInput::new(vec![
|
||||
RadioEntryData::new("Linear").label("Linear").tooltip_label("Linear Gradient").on_update(move |_| {
|
||||
GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::Form(GradientForm::Linear),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Radial").label("Radial").tooltip_label("Radial Gradient").on_update(move |_| {
|
||||
GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::Form(GradientForm::Radial),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
])
|
||||
.selected_index(Some((self.options.gradient_form == GradientForm::Radial) as u32))
|
||||
.widget_instance();
|
||||
let gradient_form_entries = RadioEntryData::list_from_choice_type(|gradient_form| {
|
||||
GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::Form(gradient_form),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
let gradient_form = RadioInput::new(gradient_form_entries).selected_index(Some(self.options.gradient_form as u32)).widget_instance();
|
||||
|
||||
// Display priority: the selected layer's stops, then any user-customized tool default, then the working colors
|
||||
let stops_value = self.data.current_gradient_stops.clone().or_else(|| self.data.default_gradient_stops.clone()).unwrap_or_else(|| {
|
||||
|
||||
@@ -217,26 +217,12 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetInstance {
|
||||
}
|
||||
|
||||
fn create_arc_type_widget(arc_type: ArcType) -> WidgetInstance {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("Open").label("Open").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::Open),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Closed").label("Closed").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::Closed),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Pie").label("Pie").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::PieSlice),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
];
|
||||
let entries = RadioEntryData::list_from_choice_type(|arc_type| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(arc_type),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_instance()
|
||||
}
|
||||
|
||||
@@ -307,20 +293,12 @@ fn create_spiral_type_widget(spiral_type: SpiralType) -> WidgetInstance {
|
||||
}
|
||||
|
||||
fn create_grid_type_widget(grid_type: GridType) -> WidgetInstance {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("Rectangular").label("Rectangular").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::GridType(GridType::Rectangular),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Isometric").label("Isometric").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::GridType(GridType::Isometric),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
];
|
||||
let entries = RadioEntryData::list_from_choice_type(|grid_type| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::GridType(grid_type),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
RadioInput::new(entries).selected_index(Some(grid_type as u32)).widget_instance()
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ use crate::messages::tool::utility_types::ToolRefreshOptions;
|
||||
use graph_craft::application_io::resource::ResourceId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::choice_type::ChoiceTypeStatic;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
|
||||
@@ -214,23 +213,12 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
|
||||
.into()
|
||||
})
|
||||
.widget_instance();
|
||||
let align_entries: Vec<_> = TextAlign::list()
|
||||
.iter()
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(item, var_meta)| {
|
||||
let align = *item;
|
||||
let entry = RadioEntryData::new(var_meta.name)
|
||||
.tooltip_label(var_meta.label)
|
||||
.tooltip_description(var_meta.description.unwrap_or_default())
|
||||
.on_update(move |_| {
|
||||
TextToolMessage::UpdateOptions {
|
||||
options: TextOptionsUpdate::Align(align),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
if let Some(icon) = var_meta.icon { entry.icon(icon) } else { entry.label(var_meta.label) }
|
||||
})
|
||||
.collect();
|
||||
let align_entries = RadioEntryData::list_from_choice_type(|align| {
|
||||
TextToolMessage::UpdateOptions {
|
||||
options: TextOptionsUpdate::Align(align),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
let align = RadioInput::new(align_entries).selected_index(Some(tool.options.align as u32)).widget_instance();
|
||||
vec![
|
||||
font,
|
||||
|
||||
@@ -19,6 +19,10 @@ import Empty12px from "/../branding/assets/icon-12px-solid/empty-12px.svg";
|
||||
import Failure from "/../branding/assets/icon-12px-solid/failure.svg";
|
||||
import FullscreenEnter from "/../branding/assets/icon-12px-solid/fullscreen-enter.svg";
|
||||
import FullscreenExit from "/../branding/assets/icon-12px-solid/fullscreen-exit.svg";
|
||||
import GradientSpreadClear from "/../branding/assets/icon-12px-solid/gradient-spread-clear.svg";
|
||||
import GradientSpreadPad from "/../branding/assets/icon-12px-solid/gradient-spread-pad.svg";
|
||||
import GradientSpreadReflect from "/../branding/assets/icon-12px-solid/gradient-spread-reflect.svg";
|
||||
import GradientSpreadRepeat from "/../branding/assets/icon-12px-solid/gradient-spread-repeat.svg";
|
||||
import GridDotted from "/../branding/assets/icon-12px-solid/grid-dotted.svg";
|
||||
import Grid from "/../branding/assets/icon-12px-solid/grid.svg";
|
||||
import Info from "/../branding/assets/icon-12px-solid/info.svg";
|
||||
@@ -67,6 +71,10 @@ const SOLID_12PX = {
|
||||
Failure: { svg: Failure, size: 12 },
|
||||
FullscreenEnter: { svg: FullscreenEnter, size: 12 },
|
||||
FullscreenExit: { svg: FullscreenExit, size: 12 },
|
||||
GradientSpreadClear: { svg: GradientSpreadClear, size: 12 },
|
||||
GradientSpreadPad: { svg: GradientSpreadPad, size: 12 },
|
||||
GradientSpreadReflect: { svg: GradientSpreadReflect, size: 12 },
|
||||
GradientSpreadRepeat: { svg: GradientSpreadRepeat, size: 12 },
|
||||
Grid: { svg: Grid, size: 12 },
|
||||
GridDotted: { svg: GridDotted, size: 12 },
|
||||
Info: { svg: Info, size: 12 },
|
||||
|
||||
@@ -89,7 +89,13 @@ export function destroyTooltipStore() {
|
||||
|
||||
// Listen for mouse movements onto tooltip-bearing HTML elements to track the future target of a tooltip
|
||||
function onMouseOver(e: MouseEvent) {
|
||||
const element = (e.target instanceof Element && e.target.closest("[data-tooltip-label], [data-tooltip-description], [data-tooltip-shortcut]")) || undefined;
|
||||
const target = (e.target instanceof Element && e.target) || undefined;
|
||||
let element = target?.closest("[data-tooltip-label], [data-tooltip-description], [data-tooltip-shortcut]") || undefined;
|
||||
|
||||
// A floating menu renders within the DOM of the widget that spawned it, so a match beyond the menu's own content
|
||||
// is the spawner's tooltip rather than one belonging to whatever the cursor is actually over
|
||||
const floatingMenuContent = target?.closest("[data-floating-menu-content]");
|
||||
if (element && floatingMenuContent && !floatingMenuContent.contains(element)) element = undefined;
|
||||
|
||||
update((state) => {
|
||||
state.visible = false;
|
||||
|
||||
@@ -11,8 +11,10 @@ use glam::{DAffine2, DVec2};
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum GradientForm {
|
||||
/// Transitions the colors along a straight line.
|
||||
#[default]
|
||||
Linear,
|
||||
/// Transitions the colors outward from a center point.
|
||||
Radial,
|
||||
}
|
||||
|
||||
@@ -788,10 +790,13 @@ impl Gradient {
|
||||
pub enum GradientSpread {
|
||||
/// Extends the end colors outward.
|
||||
#[default]
|
||||
#[icon("GradientSpreadPad")]
|
||||
Pad,
|
||||
/// Loops the gradient by mirroring back-and-forth.
|
||||
#[icon("GradientSpreadReflect")]
|
||||
Reflect,
|
||||
/// Loops the gradient as copies of itself.
|
||||
#[icon("GradientSpreadRepeat")]
|
||||
Repeat,
|
||||
// TODO: Add a "Clear" variant that returns transparent black outside the gradient's range
|
||||
}
|
||||
|
||||
@@ -155,9 +155,12 @@ pub enum GridType {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum ArcType {
|
||||
/// Leaves the two ends of the arc unconnected.
|
||||
#[default]
|
||||
Open = 0,
|
||||
/// Connects the two ends of the arc with a straight line.
|
||||
Closed,
|
||||
/// Connects the two ends of the arc to its center, forming a wedge.
|
||||
PieSlice,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user