Skip to content

Commit 4421b79

Browse files
henryksloanKeavon
andcommitted
Add two-way tool option messaging system between frontend/backend (#361)
* Add two-way tool option messaging system * Rename tool option functions * Move repeated frontend messaging code to function * Address style comments * Rename variable to be more descriptive * Move tool options update to SetActiveTool message * Refactor record of all tool options * Only pass active tool options to bar Co-authored-by: Keavon Chambers <[email protected]>
1 parent 641d534 commit 4421b79

File tree

7 files changed

+58
-31
lines changed

7 files changed

+58
-31
lines changed

editor/src/frontend/frontend_message_handler.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::frontend::layer_panel::LayerPanelEntry;
22
use crate::message_prelude::*;
3+
use crate::tool::tool_options::ToolOptions;
34
use crate::Color;
45
use serde::{Deserialize, Serialize};
56

@@ -10,7 +11,7 @@ pub type Callback = Box<dyn Fn(FrontendMessage)>;
1011
pub enum FrontendMessage {
1112
CollapseFolder { path: Vec<LayerId> },
1213
ExpandFolder { path: Vec<LayerId>, children: Vec<LayerPanelEntry> },
13-
SetActiveTool { tool_name: String },
14+
SetActiveTool { tool_name: String, tool_options: Option<ToolOptions> },
1415
SetActiveDocument { document_index: usize },
1516
UpdateOpenDocumentsList { open_documents: Vec<String> },
1617
DisplayError { description: String },

editor/src/tool/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
117117
let tool_init = |tool: ToolType| (tool, tool.default_options());
118118
std::array::IntoIter::new([
119119
tool_init(ToolType::Select),
120+
tool_init(ToolType::Pen),
121+
tool_init(ToolType::Line),
120122
tool_init(ToolType::Ellipse),
121123
tool_init(ToolType::Shape), // TODO: Add more tool defaults
122124
])
@@ -185,6 +187,8 @@ impl ToolType {
185187
fn default_options(&self) -> ToolOptions {
186188
match self {
187189
ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New },
190+
ToolType::Pen => ToolOptions::Pen { weight: 5 },
191+
ToolType::Line => ToolOptions::Line { weight: 5 },
188192
ToolType::Ellipse => ToolOptions::Ellipse,
189193
ToolType::Shape => ToolOptions::Shape {
190194
shape_type: ShapeType::Polygon { vertices: 6 },

editor/src/tool/tool_message_handler.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
108108
tool_data.active_tool_type = new_tool;
109109

110110
// Notify the frontend about the new active tool to be displayed
111-
responses.push_back(FrontendMessage::SetActiveTool { tool_name: new_tool.to_string() }.into());
111+
let tool_name = new_tool.to_string();
112+
let tool_options = self.tool_state.document_tool_data.tool_options.get(&new_tool).map(|tool_options| *tool_options);
113+
responses.push_back(FrontendMessage::SetActiveTool { tool_name, tool_options }.into());
112114
}
113115
SwapColors => {
114116
let document_data = &mut self.tool_state.document_tool_data;

frontend/src/components/panels/Document.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<Separator :type="SeparatorType.Section" />
88

9-
<ToolOptions :activeTool="activeTool" />
9+
<ToolOptions :activeTool="activeTool" :activeToolOptions="activeToolOptions" />
1010
</div>
1111
<div class="spacer"></div>
1212
<div class="right side">
@@ -330,7 +330,10 @@ export default defineComponent({
330330
331331
registerResponseHandler(ResponseType.SetActiveTool, (responseData: Response) => {
332332
const toolData = responseData as SetActiveTool;
333-
if (toolData) this.activeTool = toolData.tool_name;
333+
if (toolData) {
334+
this.activeTool = toolData.tool_name;
335+
this.activeToolOptions = toolData.tool_options;
336+
}
334337
});
335338
336339
registerResponseHandler(ResponseType.SetCanvasZoom, (responseData: Response) => {
@@ -357,6 +360,7 @@ export default defineComponent({
357360
canvasSvgWidth: "100%",
358361
canvasSvgHeight: "100%",
359362
activeTool: "Select",
363+
activeToolOptions: {},
360364
documentModeEntries,
361365
viewModeEntries,
362366
documentModeSelectionIndex: 0,

frontend/src/components/widgets/options/ToolOptions.vue

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
<template>
22
<div class="tool-options">
3-
<template v-for="(option, index) in toolOptions[activeTool] || []" :key="index">
3+
<template v-for="(option, index) in toolOptionsWidgets[activeTool] || []" :key="index">
44
<!-- TODO: Use `<component :is="" v-bind="attributesObject"></component>` to avoid all the separate components with `v-if` -->
55
<IconButton v-if="option.kind === 'IconButton'" :action="() => handleIconButtonAction(option)" :title="option.tooltip" v-bind="option.props" />
66
<PopoverButton v-if="option.kind === 'PopoverButton'" :title="option.tooltip" :action="option.callback" v-bind="option.props">
77
<h3>{{ option.popover.title }}</h3>
88
<p>{{ option.popover.text }}</p>
99
</PopoverButton>
10-
<NumberInput v-if="option.kind === 'NumberInput'" v-model:value="option.props.value" @update:value="option.callback" :title="option.tooltip" v-bind="option.props" />
10+
<NumberInput
11+
v-if="option.kind === 'NumberInput'"
12+
@update:value="(value) => updateToolOptions(option.optionPath, value)"
13+
:title="option.tooltip"
14+
:value="getToolOption(option.optionPath)"
15+
v-bind="option.props"
16+
/>
1117
<Separator v-if="option.kind === 'Separator'" v-bind="option.props" />
1218
</template>
1319
</div>
@@ -23,7 +29,7 @@
2329
</style>
2430

2531
<script lang="ts">
26-
import { defineComponent } from "vue";
32+
import { defineComponent, PropType } from "vue";
2733
2834
import { comingSoon } from "@/utilities/errors";
2935
import { WidgetRow, SeparatorType, IconButtonWidget } from "@/components/widgets/widgets";
@@ -38,28 +44,36 @@ const wasm = import("@/../wasm/pkg");
3844
export default defineComponent({
3945
props: {
4046
activeTool: { type: String },
47+
activeToolOptions: { type: Object as PropType<Record<string, object>> },
4148
},
42-
computed: {},
4349
methods: {
44-
async setShapeOptions(newValue: number) {
45-
// TODO: Each value-input widget (i.e. not a button) should map to a field in an options struct,
46-
// and updating a widget should send the whole updated struct to the backend.
47-
// Later, it could send a single-field update to the backend.
48-
49-
// This is a placeholder call, using the Shape tool as an example
50-
// eslint-disable-next-line camelcase
51-
(await wasm).set_tool_options(this.$props.activeTool || "", { Shape: { shape_type: { Polygon: { vertices: newValue } } } });
50+
async updateToolOptions(path: string[], newValue: number) {
51+
this.setToolOption(path, newValue);
52+
(await wasm).set_tool_options(this.activeTool || "", this.activeToolOptions);
5253
},
53-
async setLineOptions(newValue: number) {
54-
// eslint-disable-next-line camelcase
55-
(await wasm).set_tool_options(this.$props.activeTool || "", { Line: { weight: newValue } });
54+
async sendToolMessage(message: string | object) {
55+
(await wasm).send_tool_message(this.activeTool || "", message);
5656
},
57-
async setPenOptions(newValue: number) {
58-
// eslint-disable-next-line camelcase
59-
(await wasm).set_tool_options(this.$props.activeTool || "", { Pen: { weight: newValue } });
57+
// Traverses the given path and returns the direct parent of the option
58+
getRecordContainingOption(optionPath: string[]): Record<string, number> {
59+
const allButLast = optionPath.slice(0, -1);
60+
let currentRecord = this.activeToolOptions as Record<string, object | number>;
61+
[this.activeTool || "", ...allButLast].forEach((attr) => {
62+
currentRecord = currentRecord[attr] as Record<string, object | number>;
63+
});
64+
return currentRecord as Record<string, number>;
6065
},
61-
async sendToolMessage(message: string | object) {
62-
(await wasm).send_tool_message(this.$props.activeTool || "", message);
66+
// Traverses the given path into the active tool's option struct, and sets the value at the path tail
67+
setToolOption(optionPath: string[], newValue: number) {
68+
const last = optionPath.slice(-1)[0];
69+
const recordContainingOption = this.getRecordContainingOption(optionPath);
70+
recordContainingOption[last] = newValue;
71+
},
72+
// Traverses the given path into the active tool's option struct, and returns the value at the path tail
73+
getToolOption(optionPath: string[]): number {
74+
const last = optionPath.slice(-1)[0];
75+
const recordContainingOption = this.getRecordContainingOption(optionPath);
76+
return recordContainingOption[last];
6377
},
6478
handleIconButtonAction(option: IconButtonWidget) {
6579
if (option.message) {
@@ -76,7 +90,7 @@ export default defineComponent({
7690
},
7791
},
7892
data() {
79-
const toolOptions: Record<string, WidgetRow> = {
93+
const toolOptionsWidgets: Record<string, WidgetRow> = {
8094
Select: [
8195
{ kind: "IconButton", message: { Align: ["X", "Min"] }, tooltip: "Align Left", props: { icon: "AlignLeft", size: 24 } },
8296
{ kind: "IconButton", message: { Align: ["X", "Center"] }, tooltip: "Align Horizontal Center", props: { icon: "AlignHorizontalCenter", size: 24 } },
@@ -134,13 +148,13 @@ export default defineComponent({
134148
props: {},
135149
},
136150
],
137-
Shape: [{ kind: "NumberInput", callback: this.setShapeOptions, props: { value: 6, min: 3, isInteger: true, label: "Sides" } }],
138-
Line: [{ kind: "NumberInput", callback: this.setLineOptions, props: { value: 5, min: 1, isInteger: true, unit: " px", label: "Weight" } }],
139-
Pen: [{ kind: "NumberInput", callback: this.setPenOptions, props: { value: 5, min: 1, isInteger: true, unit: " px", label: "Weight" } }],
151+
Shape: [{ kind: "NumberInput", optionPath: ["shape_type", "Polygon", "vertices"], props: { min: 3, isInteger: true, label: "Sides" } }],
152+
Line: [{ kind: "NumberInput", optionPath: ["weight"], props: { min: 1, isInteger: true, unit: " px", label: "Weight" } }],
153+
Pen: [{ kind: "NumberInput", optionPath: ["weight"], props: { min: 1, isInteger: true, unit: " px", label: "Weight" } }],
140154
};
141155
142156
return {
143-
toolOptions,
157+
toolOptionsWidgets,
144158
SeparatorType,
145159
comingSoon,
146160
};

frontend/src/components/widgets/widgets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export interface PopoverButtonProps {
5555
export interface NumberInputWidget {
5656
kind: "NumberInput";
5757
tooltip?: string;
58-
callback?: Function;
59-
props: NumberInputProps;
58+
optionPath: string[];
59+
props: Omit<NumberInputProps, "value">;
6060
}
6161

6262
export interface NumberInputProps {

frontend/src/utilities/response-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,12 @@ function newUpdateWorkingColors(input: any): UpdateWorkingColors {
127127

128128
export interface SetActiveTool {
129129
tool_name: string;
130+
tool_options: object;
130131
}
131132
function newSetActiveTool(input: any): SetActiveTool {
132133
return {
133134
tool_name: input.tool_name,
135+
tool_options: input.tool_options,
134136
};
135137
}
136138

0 commit comments

Comments
 (0)