mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 10:48:11 +08:00
Implement tool messaging and shape flipping (#288)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-button" :class="`size-${String(size)}`">
|
<button class="icon-button" :class="`size-${String(size)}`" @click="onClick">
|
||||||
<IconLabel :icon="icon" />
|
<IconLabel :icon="icon" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,6 +60,7 @@ export default defineComponent({
|
|||||||
icon: { type: String, required: true },
|
icon: { type: String, required: true },
|
||||||
size: { type: Number, required: true },
|
size: { type: Number, required: true },
|
||||||
gapAfter: { type: Boolean, default: false },
|
gapAfter: { type: Boolean, default: false },
|
||||||
|
onClick: Function,
|
||||||
},
|
},
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tool-options">
|
<div class="tool-options">
|
||||||
<template v-for="(option, index) in optionsMap.get(activeTool) || []" :key="index">
|
<template v-for="(option, index) in optionsMap.get(activeTool) || []" :key="index">
|
||||||
<IconButton v-if="option.kind === 'icon_button'" :icon="option.icon" :size="24" :title="option.title" />
|
<IconButton v-if="option.kind === 'icon_button'" :icon="option.icon" :size="24" :title="option.title" :onClick="() => sendToolMessage(option.message)" />
|
||||||
<Separator v-if="option.kind === 'separator'" :type="option.type" />
|
<Separator v-if="option.kind === 'separator'" :type="option.type" />
|
||||||
<PopoverButton v-if="option.kind === 'popover_button'">
|
<PopoverButton v-if="option.kind === 'popover_button'">
|
||||||
<h3>{{ option.title }}</h3>
|
<h3>{{ option.title }}</h3>
|
||||||
@@ -38,6 +38,7 @@ interface IconButtonOption {
|
|||||||
kind: "icon_button";
|
kind: "icon_button";
|
||||||
icon: string;
|
icon: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeparatorOption {
|
interface SeparatorOption {
|
||||||
@@ -74,6 +75,12 @@ export default defineComponent({
|
|||||||
// This is a placeholder call, using the Shape tool as an example
|
// This is a placeholder call, using the Shape tool as an example
|
||||||
set_tool_options(this.$props.activeTool || "", { Shape: { shape_type: { Polygon: { vertices: newValue } } } });
|
set_tool_options(this.$props.activeTool || "", { Shape: { shape_type: { Polygon: { vertices: newValue } } } });
|
||||||
},
|
},
|
||||||
|
async sendToolMessage(message?: string) {
|
||||||
|
if (message) {
|
||||||
|
const { send_tool_message } = await wasm;
|
||||||
|
send_tool_message(this.$props.activeTool || "", message);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const optionsMap: ToolOptionsMap = new Map([
|
const optionsMap: ToolOptionsMap = new Map([
|
||||||
@@ -96,8 +103,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
{ kind: "separator", type: SeparatorType.Section },
|
{ kind: "separator", type: SeparatorType.Section },
|
||||||
|
|
||||||
{ kind: "icon_button", icon: "FlipHorizontal", title: "Flip Horizontal" },
|
{ kind: "icon_button", icon: "FlipHorizontal", title: "Flip Horizontal", message: "FlipHorizontal" },
|
||||||
{ kind: "icon_button", icon: "FlipVertical", title: "Flip Vertical" },
|
{ kind: "icon_button", icon: "FlipVertical", title: "Flip Vertical", message: "FlipVertical" },
|
||||||
|
|
||||||
{ kind: "separator", type: SeparatorType.Related },
|
{ kind: "separator", type: SeparatorType.Related },
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::EDITOR_STATE;
|
|||||||
use editor_core::input::input_preprocessor::ModifierKeys;
|
use editor_core::input::input_preprocessor::ModifierKeys;
|
||||||
use editor_core::input::mouse::ScrollDelta;
|
use editor_core::input::mouse::ScrollDelta;
|
||||||
use editor_core::message_prelude::*;
|
use editor_core::message_prelude::*;
|
||||||
use editor_core::tool::tool_options::ToolOptions;
|
use editor_core::tool::{tool_options::ToolOptions, tools, ToolType};
|
||||||
use editor_core::{
|
use editor_core::{
|
||||||
input::mouse::{MouseState, ViewportPosition},
|
input::mouse::{MouseState, ViewportPosition},
|
||||||
LayerId,
|
LayerId,
|
||||||
@@ -30,12 +30,31 @@ pub fn set_tool_options(tool: String, options: &JsValue) -> Result<(), JsValue>
|
|||||||
match options.into_serde::<ToolOptions>() {
|
match options.into_serde::<ToolOptions>() {
|
||||||
Ok(options) => EDITOR_STATE.with(|editor| match translate_tool(&tool) {
|
Ok(options) => EDITOR_STATE.with(|editor| match translate_tool(&tool) {
|
||||||
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SetToolOptions(tool, options)).map_err(convert_error),
|
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SetToolOptions(tool, options)).map_err(convert_error),
|
||||||
None => Err(Error::new(&format!("Couldn't select {} because it was not recognized as a valid tool", tool)).into()),
|
None => Err(Error::new(&format!("Couldn't set options for {} because it was not recognized as a valid tool", tool)).into()),
|
||||||
}),
|
}),
|
||||||
Err(err) => Err(Error::new(&format!("Invalud JSON for ToolOptions: {}", err)).into()),
|
Err(err) => Err(Error::new(&format!("Invalid JSON for ToolOptions: {}", err)).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a message to a given tool
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn send_tool_message(tool: String, message: &JsValue) -> Result<(), JsValue> {
|
||||||
|
let tool_message = match translate_tool(&tool) {
|
||||||
|
Some(tool) => match tool {
|
||||||
|
ToolType::Select => match message.into_serde::<tools::select::SelectMessage>() {
|
||||||
|
Ok(select_message) => Ok(ToolMessage::Select(select_message)),
|
||||||
|
Err(err) => Err(Error::new(&format!("Invalid message for {}: {}", tool, err)).into()),
|
||||||
|
},
|
||||||
|
_ => Err(Error::new(&format!("Tool message sending not implemented for {}", tool)).into()),
|
||||||
|
},
|
||||||
|
None => Err(Error::new(&format!("Couldn't send message for {} because it was not recognized as a valid tool", tool)).into()),
|
||||||
|
};
|
||||||
|
EDITOR_STATE.with(|editor| match tool_message {
|
||||||
|
Ok(tool_message) => editor.borrow_mut().handle_message(tool_message).map_err(convert_error),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_document(document: usize) -> Result<(), JsValue> {
|
pub fn select_document(document: usize) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectDocument(document)).map_err(convert_error))
|
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectDocument(document)).map_err(convert_error))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use document_core::layers::Layer;
|
use document_core::layers::Layer;
|
||||||
use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation};
|
use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||||
use glam::DAffine2;
|
use glam::{DAffine2, DVec2};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
@@ -55,6 +55,7 @@ pub enum DocumentMessage {
|
|||||||
SetCanvasRotation(f64),
|
SetCanvasRotation(f64),
|
||||||
NudgeSelectedLayers(f64, f64),
|
NudgeSelectedLayers(f64, f64),
|
||||||
ReorderSelectedLayers(i32),
|
ReorderSelectedLayers(i32),
|
||||||
|
FlipLayer(Vec<LayerId>, bool, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DocumentOperation> for DocumentMessage {
|
impl From<DocumentOperation> for DocumentMessage {
|
||||||
@@ -594,6 +595,18 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
responses.push_back(DocumentMessage::SelectLayers(selected_layer_paths).into());
|
responses.push_back(DocumentMessage::SelectLayers(selected_layer_paths).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FlipLayer(path, flip_horizontal, flip_vertical) => {
|
||||||
|
if let Ok(layer) = self.active_document_mut().document.layer_mut(&path) {
|
||||||
|
let scale = DVec2::new(if flip_horizontal { -1. } else { 1. }, if flip_vertical { -1. } else { 1. });
|
||||||
|
responses.push_back(
|
||||||
|
DocumentOperation::SetLayerTransform {
|
||||||
|
path,
|
||||||
|
transform: (layer.transform * DAffine2::from_scale(scale)).to_cols_array(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
|
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use document_core::layers::style::Fill;
|
|||||||
use document_core::layers::style::Stroke;
|
use document_core::layers::style::Stroke;
|
||||||
use document_core::Operation;
|
use document_core::Operation;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||||
@@ -16,12 +17,15 @@ pub struct Select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Select)]
|
#[impl_message(Message, ToolMessage, Select)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum SelectMessage {
|
pub enum SelectMessage {
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
MouseMove,
|
MouseMove,
|
||||||
Abort,
|
Abort,
|
||||||
|
|
||||||
|
FlipHorizontal,
|
||||||
|
FlipVertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
||||||
@@ -121,6 +125,22 @@ impl Fsm for SelectToolFsmState {
|
|||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
(_, FlipHorizontal) => {
|
||||||
|
let selected_layers = document.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone()));
|
||||||
|
for path in selected_layers {
|
||||||
|
responses.push_back(DocumentMessage::FlipLayer(path, true, false).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, FlipVertical) => {
|
||||||
|
let selected_layers = document.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone()));
|
||||||
|
for path in selected_layers {
|
||||||
|
responses.push_back(DocumentMessage::FlipLayer(path, false, true).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user