mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 03:08:13 +08:00
Make the dropdown widget's file drop a callback and keep stray file drops from navigating the browser away (#4553)
* Make the dropdown widget's file drop a callback and keep stray file drops from navigating the browser away * Look up the dropped file's widget before reading the file
This commit is contained in:
@@ -30,4 +30,9 @@ pub enum LayoutMessage {
|
||||
layout_target: LayoutTarget,
|
||||
widget_id: WidgetId,
|
||||
},
|
||||
WidgetValueFileDrop {
|
||||
layout_target: LayoutTarget,
|
||||
widget_id: WidgetId,
|
||||
file: DroppedFile,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -76,6 +76,21 @@ impl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHa
|
||||
responses.add((icon_button.on_drag_drop.callback)(icon_button));
|
||||
}
|
||||
}
|
||||
LayoutMessage::WidgetValueFileDrop { layout_target, widget_id, file } => {
|
||||
let Some(layout) = self.layouts.get_mut(layout_target as usize) else {
|
||||
warn!("WidgetValueFileDrop referenced an invalid layout. `widget_id: {widget_id}`, `layout_target: {layout_target:?}`");
|
||||
return;
|
||||
};
|
||||
let Some(widget_instance) = layout.iter_mut().find(|widget| widget.widget_id == widget_id) else {
|
||||
warn!("WidgetValueFileDrop referenced an invalid widget ID. `widget_id: {widget_id}`, `layout_target: {layout_target:?}`");
|
||||
return;
|
||||
};
|
||||
if let Widget::DropdownInput(dropdown_input) = &*widget_instance.widget
|
||||
&& dropdown_input.takes_file_drop
|
||||
{
|
||||
responses.add((dropdown_input.on_file_drop.callback)(&file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 crate::messages::portfolio::ingest::utility_types::IngestAction;
|
||||
use derivative::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::color::SRGBA8;
|
||||
@@ -84,9 +83,10 @@ pub struct DropdownInput {
|
||||
pub virtual_scrolling: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub interactive: bool,
|
||||
// Where a file dropped on the widget is sent, which also makes the widget take dropped files
|
||||
#[serde(rename = "fileDropAction")]
|
||||
pub file_drop_action: Option<IngestAction>,
|
||||
// Set along with the `on_file_drop` callback
|
||||
#[serde(rename = "takesFileDrop")]
|
||||
#[widget_builder(skip)]
|
||||
pub takes_file_drop: bool,
|
||||
|
||||
// Sizing
|
||||
#[serde(rename = "minWidth")]
|
||||
@@ -101,8 +101,28 @@ pub struct DropdownInput {
|
||||
pub tooltip_description: String,
|
||||
#[serde(rename = "tooltipShortcut")]
|
||||
pub tooltip_shortcut: Option<ActionShortcut>,
|
||||
//
|
||||
// Callbacks exists on the `MenuListEntry` children, not this parent `DropdownInput`
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
#[widget_builder(skip)]
|
||||
pub on_file_drop: WidgetCallback<DroppedFile>,
|
||||
}
|
||||
|
||||
impl DropdownInput {
|
||||
/// Makes the widget take a file dropped on it, which is handed to the callback.
|
||||
pub fn on_file_drop(mut self, callback: impl Fn(&DroppedFile) -> Message + 'static + Send + Sync) -> Self {
|
||||
self.takes_file_drop = true;
|
||||
self.on_file_drop = WidgetCallback::new(callback);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DroppedFile {
|
||||
pub name: String,
|
||||
pub mime_type: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub type MenuListEntrySections = Vec<Vec<MenuListEntry>>;
|
||||
|
||||
@@ -39,6 +39,7 @@ use graphene_std::vector::style::{
|
||||
};
|
||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
||||
use graphene_std::{NodeParameter, ParameterRef};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
let widget = TextLabel::new(text).widget_instance();
|
||||
@@ -1390,7 +1391,15 @@ pub fn resource_widget(parameter_widgets_info: ParameterWidgetsInfo, filters: Ve
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
DropdownInput::new(vec![vec![none, browse], file_entries])
|
||||
.selected_index(selected_index)
|
||||
.file_drop_action(Some(file_drop_action))
|
||||
.on_file_drop(move |file| {
|
||||
IngestMessage::Ingest {
|
||||
data: file.data.clone(),
|
||||
action: file_drop_action.clone(),
|
||||
mime_type: file.mime_type.clone(),
|
||||
path: Some(PathBuf::from(&file.name)),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_instance(),
|
||||
]);
|
||||
widgets
|
||||
|
||||
@@ -342,6 +342,7 @@ mod tests {
|
||||
let png = Image::new(8, 8, Color::WHITE).to_png();
|
||||
let refusal = |data: &[u8]| {
|
||||
let responses = ingest(data, resource_input(TypeFilter::raster().types), true);
|
||||
assert_eq!(responses.len(), 1, "a refused file should only show a dialog");
|
||||
match &responses[0] {
|
||||
Message::Dialog(DialogMessage::DisplayDialogError { description, .. }) => description.clone(),
|
||||
_ => panic!("the user should be told why"),
|
||||
|
||||
Reference in New Issue
Block a user