diff --git a/src/components/field/svg/FieldOverlayRoot.tsx b/src/components/field/svg/FieldOverlayRoot.tsx index 79b00a6475..b25e5c01fa 100644 --- a/src/components/field/svg/FieldOverlayRoot.tsx +++ b/src/components/field/svg/FieldOverlayRoot.tsx @@ -242,6 +242,8 @@ class FieldOverlayRoot extends Component { )} + + {layers[ViewLayers.Grid] && } {/* Waypoint mouse capture*/} @@ -304,7 +306,6 @@ class FieldOverlayRoot extends Component { )} - {layers[ViewLayers.Waypoints] && uiState.isNavbarWaypointSelected() && ( { {layers[ViewLayers.Samples] && layers[ViewLayers.Trajectory] && ( )} + {/* Display field zones */} + {layers[ViewLayers.Zones] && + doc.pathlist.activePath.params.constraints + .filter((c) => c.enabled) + .map((c) => { + return ( + } + lineColor={c.selected ? "var(--select-yellow)":"transparent"} + clickable= {c.selected || !(uiState.layers[ViewLayers.Waypoints] && + uiState.isNavbarWaypointSelected())} + > + ); + })} + + {!layers[ViewLayers.Zones] && doc.isSidebarConstraintSelected && ( + + } + lineColor="var(--select-yellow)" + clickable={true} + > + )} + {!doc.isSidebarConstraintSelected && + doc.isSidebarConstraintHovered && ( + + } + lineColor="white" + clickable={false} + > + )} {layers[ViewLayers.Waypoints] && doc.pathlist.activePath.params.waypoints @@ -364,26 +402,7 @@ class FieldOverlayRoot extends Component { {eventMarkerSelected && ( )} - {doc.isSidebarConstraintSelected && ( - - } - lineColor="var(--select-yellow)" - > - )} - {!doc.isSidebarConstraintSelected && - doc.isSidebarConstraintHovered && ( - - } - lineColor="white" - > - )} {layers[ViewLayers.Trajectory] && ( = { data: IConstraintStoreKeyed; start: IHolonomicWaypointStore; end: IHolonomicWaypointStore; lineColor: string; + select: ()=>void; + clickable: boolean; +}; +export type OverlayElementProps = { + data: IConstraintDataStore; + start?: IHolonomicWaypointStore; + end?: IHolonomicWaypointStore; + selected: boolean; + select: ()=>void; + clickable: boolean; }; const overlays = { PointAt: (props: OverlayProps<"PointAt">) => ( @@ -26,6 +37,9 @@ const overlays = { start={props.start} end={props.end} lineColor={props.lineColor} + selected={props.data.selected} + select={()=>{props.data.setSelected(true)}} + clickable={props.clickable} > ), KeepInCircle: (props: OverlayProps<"KeepInCircle">) => ( @@ -33,7 +47,9 @@ const overlays = { data={props.data.data} start={props.start} end={props.end} - lineColor={props.lineColor} + selected={props.data.selected} + select={()=>{props.data.setSelected(true)}} + clickable={props.clickable} > ), KeepInRectangle: (props: OverlayProps<"KeepInRectangle">) => ( @@ -41,7 +57,9 @@ const overlays = { data={props.data.data} start={props.start} end={props.end} - lineColor={props.lineColor} + selected={props.data.selected} + select={()=>{props.data.setSelected(true)}} + clickable={props.clickable} > ), KeepInLane: (props: OverlayProps<"KeepInLane">) => ( @@ -49,6 +67,9 @@ const overlays = { data={props.data.data} start={props.start} end={props.end} + selected={props.data.selected} + select={()=>{props.data.setSelected(true)}} + clickable={props.clickable} > ), KeepOutCircle: (props: OverlayProps<"KeepOutCircle">) => ( @@ -56,7 +77,9 @@ const overlays = { data={props.data.data} start={props.start} end={props.end} - lineColor={props.lineColor} + selected={props.data.selected} + select={()=>{props.data.setSelected(true)}} + clickable={props.clickable} > ), StopPoint: () => <>, @@ -70,6 +93,7 @@ type Props = { constraint?: IConstraintStoreKeyed; points: IHolonomicWaypointStore[]; lineColor: string; + clickable: boolean; }; function FieldConstraintDisplayLayer(props: Props) { const constraint = props.constraint; @@ -82,19 +106,20 @@ function FieldConstraintDisplayLayer(props: Props) { data: constraint, start: constraint.getStartWaypoint(props.points), end: constraint.getEndWaypoint(props.points), - lineColor: props.lineColor + lineColor: props.lineColor, + clickable: props.clickable }; if (startIndex === undefined) { return <>; } return ( - + {overlays[constraint.data.type]( // @ts-expect-error can't cast the constraint as the proper type. diff --git a/src/components/field/svg/constraintDisplay/KeepInCircleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInCircleOverlay.tsx index 416bb30d8c..a2d089f610 100644 --- a/src/components/field/svg/constraintDisplay/KeepInCircleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInCircleOverlay.tsx @@ -6,19 +6,19 @@ import { ConstraintKey, DataMap } from "../../../../document/ConstraintDefinitions"; -import { doc } from "../../../../document/DocumentManager"; +import { doc, uiState } from "../../../../document/DocumentManager"; import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; +import { ViewLayers } from "../../../../document/UIData"; +import { OverlayElementProps } from "./FieldConstraintDisplayLayer"; const STROKE = 0.1; const DOT = 0.1; +const SELECT_COLOR = "var(--select-yellow)"; +const MOVABLE_COLOR = "green"; +const IMMOVABLE_COLOR = "darkseagreen"; -type Props = { - data: IConstraintDataStore; - start?: IHolonomicWaypointStore; - end?: IHolonomicWaypointStore; - lineColor: string; -}; -class KeepInCircleOverlay extends Component, object> { +class KeepInCircleOverlay extends Component, object> { + id = crypto.randomUUID(); rootRef: React.RefObject = React.createRef(); componentDidMount() { if (this.rootRef.current) { @@ -27,66 +27,79 @@ class KeepInCircleOverlay extends Component, object> { .on("drag", (event) => this.dragPointTranslate(event)) .on("start", () => { doc.history.startGroup(() => {}); + this.props.select() }) .on("end", (_event) => doc.history.stopGroup()) .container(this.rootRef.current); - d3.select(`#dragTarget-keepInCircle`).call( - dragHandleDrag - ); d3.select( - `#dragTarget-keepInCircleDot` + `#dragTarget-keepInCircle` + this.id + ).call(dragHandleDrag); + d3.select( + `#dragTarget-keepInCircleDot` + this.id ).call(dragHandleDrag); const radiusHandleDrag = d3 .drag() .on("drag", (event) => this.dragPointRadius(event)) .on("start", () => { doc.history.startGroup(() => {}); + this.props.select(); }) .on("end", (_event) => doc.history.stopGroup()) .container(this.rootRef.current); d3.select( - `#dragRadiusTarget-keepInCircle` + `#dragRadiusTarget-keepInCircle` + this.id ).call(radiusHandleDrag); } } dragPointTranslate(event: any) { - this.props.data.x.set(this.props.data.serialize.props.x.val + event.dx); - this.props.data.y.set(this.props.data.serialize.props.y.val + event.dy); + this.props.data.x.set(this.props.data.serialize.props.x.val + event.dx); + this.props.data.y.set(this.props.data.serialize.props.y.val + event.dy); } dragPointRadius(event: any) { - const dx = event.x - this.props.data.serialize.props.x.val; - const dy = event.y - this.props.data.serialize.props.y.val; - const r = Math.sqrt(dx * dx + dy * dy); + const dx = event.x - this.props.data.serialize.props.x.val; + const dy = event.y - this.props.data.serialize.props.y.val; + const r = Math.sqrt(dx * dx + dy * dy); - this.props.data.r.set(r); + this.props.data.r.set(r); } + getColor() : string { + if (this.props.selected) return SELECT_COLOR; + if (this.props.clickable) return MOVABLE_COLOR; + return IMMOVABLE_COLOR; + } render() { const data = this.props.data.serialize as DataMap["KeepInCircle"]; const x = data.props.x.val; const y = data.props.y.val; const r = data.props.r.val; + const color = this.getColor(); return ( - + { + if (this.props.clickable) this.props.select(); + }}> {/* Main Circle */} {/* Center Dot */} {/* Radius Handle */} , object> { r={r - STROKE / 2} fill={"transparent"} pointerEvents={"visibleStroke"} - stroke={"green"} + stroke={color} strokeWidth={STROKE} strokeOpacity={1.0} - id="dragRadiusTarget-keepInCircle" + id={"dragRadiusTarget-keepInCircle" + this.id} > ); diff --git a/src/components/field/svg/constraintDisplay/KeepInLaneOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInLaneOverlay.tsx index d1fdf1e50c..18d458a856 100644 --- a/src/components/field/svg/constraintDisplay/KeepInLaneOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInLaneOverlay.tsx @@ -7,17 +7,18 @@ import { import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; import * as d3 from "d3"; import { observer } from "mobx-react"; -import { doc } from "../../../../document/DocumentManager"; +import { doc, uiState } from "../../../../document/DocumentManager"; +import { ViewLayers } from "../../../../document/UIData"; +import { OverlayElementProps } from "./FieldConstraintDisplayLayer"; const STROKE = 0.02; const DOT = 0.1; +const SELECT_COLOR = "var(--select-yellow)"; +const MOVABLE_COLOR = "green"; +const IMMOVABLE_COLOR = "darkseagreen"; -type Props = { - data: IConstraintDataStore; - start?: IHolonomicWaypointStore; - end?: IHolonomicWaypointStore; -}; -class KeepInLaneOverlay extends Component, object> { +class KeepInLaneOverlay extends Component, object> { + id = crypto.randomUUID(); rootRef: React.RefObject = React.createRef(); componentDidMount() { if (this.rootRef.current) { @@ -26,16 +27,17 @@ class KeepInLaneOverlay extends Component, object> { .on("drag", (event) => this.dragPointTolerance(event)) .on("start", () => { doc.history.startGroup(() => {}); + this.props.select(); }) .on("end", (_event) => { doc.history.stopGroup(); }) .container(this.rootRef.current); d3.select( - `#dragTarget-keepInLaneAbove` + `#dragTarget-keepInLaneAbove` + this.id ).call(dragHandleDrag); d3.select( - `#dragTarget-keepInLaneBelow` + `#dragTarget-keepInLaneBelow` + this.id ).call(dragHandleDrag); } } @@ -59,6 +61,13 @@ class KeepInLaneOverlay extends Component, object> { Math.hypot(dx, dy); data.tolerance.set(dist); } + + getColor() : string { + if (this.props.selected) return SELECT_COLOR; + if (this.props.clickable) return MOVABLE_COLOR; + return IMMOVABLE_COLOR; + } + render() { const data = this.props.data.serialize as DataMap["KeepInLane"]; const tolerance = data.props.tolerance.val + STROKE / 2; @@ -94,8 +103,12 @@ class KeepInLaneOverlay extends Component, object> { (endBelowX + startBelowX) / 2, (endBelowY + startBelowY) / 2 ]; + const color = this.getColor(); return ( - + { + if (this.props.clickable) this.props.select(); + }}> {/* Lines */} , object> { x2={endAboveX} y1={startAboveY} y2={endAboveY} - stroke="green" + stroke={color} strokeWidth={STROKE} strokeOpacity={1.0} id="line-keepInLaneAbove" + pointerEvents={"none"} > ); diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 8666c821a2..f95e3ad10d 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -6,99 +6,82 @@ import { ConstraintKey, DataMap } from "../../../../document/ConstraintDefinitions"; -import { doc } from "../../../../document/DocumentManager"; +import { doc, uiState } from "../../../../document/DocumentManager"; import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; +import { ViewLayers } from "../../../../document/UIData"; +import { OverlayElementProps } from "./FieldConstraintDisplayLayer"; const STROKE = 0.1; const DOT = 0.1; +const SELECT_COLOR = "var(--select-yellow)"; +const MOVABLE_COLOR = "green"; +const IMMOVABLE_COLOR = "darkseagreen"; -type Props = { - data: IConstraintDataStore; - start?: IHolonomicWaypointStore; - end?: IHolonomicWaypointStore; - lineColor: string; -}; class KeepInRectangleOverlay extends Component< - Props<"KeepInRectangle">, + OverlayElementProps<"KeepInRectangle">, object > { + id = crypto.randomUUID(); rootRef: React.RefObject = React.createRef(); componentDidMount() { if (this.rootRef.current) { + const startDrag = (event: DragEvent) => { + doc.history.startGroup(() => { }); + this.props.select(); + }; + const endDrag = () => { + this.fixWidthHeight(); + doc.history.stopGroup(); + }; // Theres probably a better way to do this const dragHandleDrag = d3 .drag() .on("drag", (event) => this.dragPointTranslate(event, false, false)) - .on("start", () => { - doc.history.startGroup(() => {}); - }) - .on("end", (_event) => { - this.fixWidthHeight(); - doc.history.stopGroup(); - }) + .on("start", startDrag) + .on("end", endDrag) .container(this.rootRef.current); d3.select( - `#dragTarget-keepInRectangle` + `#dragTarget-keepInRectangle` + this.id ).call(dragHandleDrag); const dragHandleDragW = d3 .drag() .on("drag", (event) => this.dragPointTranslate(event, true, false)) - .on("start", () => { - doc.history.startGroup(() => {}); - }) - .on("end", (_event) => { - this.fixWidthHeight(); - doc.history.stopGroup(); - }) + .on("start", startDrag) + .on("end", endDrag) .container(this.rootRef.current); d3.select( - `#dragTarget-keepInRectangleW` + `#dragTarget-keepInRectangleW` + this.id ).call(dragHandleDragW); const dragHandleDragWH = d3 .drag() .on("drag", (event) => this.dragPointTranslate(event, true, true)) - .on("start", () => { - doc.history.startGroup(() => {}); - }) - .on("end", (_event) => { - this.fixWidthHeight(); - doc.history.stopGroup(); - }) + .on("start", startDrag) + .on("end", endDrag) .container(this.rootRef.current); d3.select( - `#dragTarget-keepInRectangleWH` + `#dragTarget-keepInRectangleWH` + this.id ).call(dragHandleDragWH); const dragHandleDragH = d3 .drag() .on("drag", (event) => this.dragPointTranslate(event, false, true)) - .on("start", () => { - doc.history.startGroup(() => {}); - }) - .on("end", (_event) => { - this.fixWidthHeight(); - doc.history.stopGroup(); - }) + .on("start", startDrag) + .on("end", endDrag) .container(this.rootRef.current); d3.select( - `#dragTarget-keepInRectangleH` + `#dragTarget-keepInRectangleH` + this.id ).call(dragHandleDragH); const dragHandleRegion = d3 - .drag() + .drag() .on("drag", (event) => this.dragRegionTranslate(event)) - .on("start", () => { - doc.history.startGroup(() => {}); - }) - .on("end", (_event) => { - this.fixWidthHeight(); - doc.history.stopGroup(); - }) + .on("start", startDrag) + .on("end", endDrag) .container(this.rootRef.current); - d3.select( - `#dragTarget-keepInRectangleRegion` + d3.select( + `#dragTarget-keepInRectangleRegion` + this.id ).call(dragHandleRegion); } } @@ -124,7 +107,7 @@ class KeepInRectangleOverlay extends Component< if (this.props.data.serialize.props.w.val < 0.0) { this.props.data.x.set( this.props.data.serialize.props.x.val + - this.props.data.serialize.props.w.val + this.props.data.serialize.props.w.val ); this.props.data.w.set(-this.props.data.serialize.props.w.val); } @@ -132,29 +115,41 @@ class KeepInRectangleOverlay extends Component< if (this.props.data.serialize.props.h.val < 0.0) { this.props.data.y.set( this.props.data.serialize.props.y.val + - this.props.data.serialize.props.h.val + this.props.data.serialize.props.h.val ); this.props.data.h.set(-this.props.data.serialize.props.h.val); } } + getColor(): string { + if (this.props.selected) return SELECT_COLOR; + if (this.props.clickable) return MOVABLE_COLOR; + return IMMOVABLE_COLOR; + } render() { const data = this.props.data.serialize as DataMap["KeepInRectangle"]; const x = data.props.x.val; const y = data.props.y.val; const w = data.props.w.val; const h = data.props.h.val; + let color = this.getColor(); + let movable = this.props.selected || this.props.clickable; + let visible = movable ? "visible" : "none"; + let visibleStroke = movable ? "visibleStroke" : "none"; return ( - + { + if (this.props.clickable) { this.props.select(); } + }} > {/* Fill Rect*/} = 0 ? x : x + w} y={h >= 0 ? y : y + h} width={Math.abs(w)} height={Math.abs(h)} - fill={"green"} - fillOpacity={0.1} - id="dragTarget-keepInRectangleRegion" + fill={color} + fillOpacity={0.05} + pointerEvents={"none"} > {/*Border Rect*/} {/* Corners */} ); diff --git a/src/components/field/svg/constraintDisplay/KeepOutCircleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepOutCircleOverlay.tsx index c163c55386..3f3565431c 100644 --- a/src/components/field/svg/constraintDisplay/KeepOutCircleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepOutCircleOverlay.tsx @@ -6,19 +6,19 @@ import { ConstraintKey, DataMap } from "../../../../document/ConstraintDefinitions"; -import { doc } from "../../../../document/DocumentManager"; +import { doc, uiState } from "../../../../document/DocumentManager"; import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; +import { ViewLayers } from "../../../../document/UIData"; +import { OverlayElementProps } from "./FieldConstraintDisplayLayer"; const STROKE = 0.1; const DOT = 0.1; +const SELECT_COLOR = "var(--select-yellow)"; +const MOVABLE_COLOR = "red"; +const IMMOVABLE_COLOR = "rosybrown"; -type Props = { - data: IConstraintDataStore; - start?: IHolonomicWaypointStore; - end?: IHolonomicWaypointStore; - lineColor: string; -}; -class KeepOutCircleOverlay extends Component, object> { +class KeepOutCircleOverlay extends Component, object> { + id = crypto.randomUUID(); rootRef: React.RefObject = React.createRef(); componentDidMount() { if (this.rootRef.current) { @@ -26,26 +26,28 @@ class KeepOutCircleOverlay extends Component, object> { .drag() .on("drag", (event) => this.dragPointTranslate(event)) .on("start", () => { - doc.history.startGroup(() => {}); + doc.history.startGroup(() => { }); + this.props.select(); }) .on("end", (_event) => doc.history.stopGroup()) .container(this.rootRef.current); - d3.select(`#dragTarget-keepOutCircle`).call( - dragHandleDrag - ); d3.select( - `#dragTarget-keepOutCircleDot` + `#dragTarget-keepOutCircle` + this.id + ).call(dragHandleDrag); + d3.select( + `#dragTarget-keepOutCircleDot` + this.id ).call(dragHandleDrag); const radiusHandleDrag = d3 .drag() .on("drag", (event) => this.dragPointRadius(event)) .on("start", () => { - doc.history.startGroup(() => {}); + doc.history.startGroup(() => { }); + this.props.select(); }) .on("end", (_event) => doc.history.stopGroup()) .container(this.rootRef.current); d3.select( - `#dragRadiusTarget-keepOutCircle` + `#dragRadiusTarget-keepOutCircle` + this.id ).call(radiusHandleDrag); } } @@ -63,30 +65,42 @@ class KeepOutCircleOverlay extends Component, object> { this.props.data.r.set(r); } + getColor(): string { + if (this.props.selected) return SELECT_COLOR; + if (this.props.clickable) return MOVABLE_COLOR; + return IMMOVABLE_COLOR; + } + render() { const data = this.props.data.serialize as DataMap["KeepOutCircle"]; const x = data.props.x.val; const y = data.props.y.val; const r = data.props.r.val; + let color = this.getColor(); return ( - + { + if (this.props.clickable) this.props.select(); + }}> {/* Main Circle */} {/* Center Dot */} {/* Radius Handle */} , object> { r={r - STROKE / 2} fill={"transparent"} pointerEvents={"visibleStroke"} - stroke={"red"} + stroke={color} strokeWidth={STROKE} strokeOpacity={1.0} - id="dragRadiusTarget-keepOutCircle" + id={"dragRadiusTarget-keepOutCircle" + this.id} > ); diff --git a/src/components/field/svg/constraintDisplay/PointAtOverlay.tsx b/src/components/field/svg/constraintDisplay/PointAtOverlay.tsx index e51b63902b..f5a2dde7ba 100644 --- a/src/components/field/svg/constraintDisplay/PointAtOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/PointAtOverlay.tsx @@ -8,14 +8,9 @@ import { } from "../../../../document/ConstraintDefinitions"; import { doc } from "../../../../document/DocumentManager"; import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; +import { OverlayElementProps } from "./FieldConstraintDisplayLayer"; -type Props = { - data: IConstraintDataStore; - start?: IHolonomicWaypointStore; - end?: IHolonomicWaypointStore; - lineColor: string; -}; -class PointAtOverlay extends Component, object> { +class PointAtOverlay extends Component & {lineColor: string}, object> { rootRef: React.RefObject = React.createRef(); componentDidMount() { if (this.rootRef.current) { @@ -24,6 +19,7 @@ class PointAtOverlay extends Component, object> { .on("drag", (event) => this.dragPointTranslate(event)) .on("start", () => { doc.history.startGroup(() => {}); + this.props.select(); }) .on("end", (_event) => doc.history.stopGroup()) .container(this.rootRef.current); @@ -33,21 +29,25 @@ class PointAtOverlay extends Component, object> { } } dragPointTranslate(event: any) { - this.props.data.x.set(event.x); - this.props.data.y.set(event.y); + this.props.data.x.set(event.x); + this.props.data.y.set(event.y); } render() { if (this.props.start === undefined) { return <>; } const data = this.props.data.serialize as DataMap["PointAt"]; + const lineColor = this.props.selected ? "var(--select-yellow)" : "white" return ( - + { + if (this.props.clickable) this.props.select(); + }}> @@ -56,9 +56,10 @@ class PointAtOverlay extends Component, object> { cx={data.props.x.val} cy={data.props.y.val} r={0.2} - stroke={this.props.lineColor} + stroke={lineColor} strokeWidth={0.02} fill="transparent" + pointerEvents={"visible"} > ); diff --git a/src/document/UIData.tsx b/src/document/UIData.tsx index 61673817d0..2a2860229f 100644 --- a/src/document/UIData.tsx +++ b/src/document/UIData.tsx @@ -1,4 +1,5 @@ import { + BorderOuter, Circle, CircleOutlined, CropFree, @@ -148,6 +149,12 @@ export const ViewData = { name: "Focus", icon: , default: false + }, + Zones: { + index: 6, + name: "Zones", + icon: , + default: true } };