Skip to content

Commit

Permalink
things can be contained in windows
Browse files Browse the repository at this point in the history
  • Loading branch information
timgott committed Sep 2, 2024
1 parent eafb474 commit 28fa0da
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 79 deletions.
11 changes: 0 additions & 11 deletions localgraphs/src/interaction/operators.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Rect } from "../../../shared/rectangle"

import { Positioned } from "../../../shared/vector"
import { WindowController } from "./windows"

/* Decision operators */

Expand Down Expand Up @@ -142,16 +141,6 @@ const operatorWindows = {
}
} as const

export function createOperatorWindow(op: OperatorNode): WindowController {
const cfg = operatorWindows[op.kind]
const area = Rect.fromSize(op.x, op.y, cfg.width, cfg.height)
return new WindowController(area, (ctx, bounds, titleArea) => {
op.x = bounds.left // store position in node such that window can be restored
op.y = bounds.top
cfg.draw(ctx, op, bounds, titleArea)
})
}

export function getInputs(op: OperatorNode): InputNode[] {
return Object.values(op.inputs)
}
Expand Down
115 changes: 92 additions & 23 deletions localgraphs/src/interaction/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@ import { Rect } from "../../../shared/rectangle";
import { ensured } from "../../../shared/utils";
import { AnimationFrame, InteractiveSystem, MouseDownResponse, PointerId, SleepState } from "./controller";

function drawWindowFrame(ctx: CanvasRenderingContext2D, bounds: Rect, headerHeight: number) {
function drawWindowFrame(ctx: CanvasRenderingContext2D, window: WindowBounds, titleArea: Rect) {
const cornerRadius = 4
ctx.strokeStyle = "darkblue";
ctx.fillStyle = `rgba(200, 220, 255, 0.6)`;
ctx.lineWidth = 1;
ctx.beginPath()
ctx.roundRect(bounds.left, bounds.top, Rect.width(bounds), headerHeight, [cornerRadius, cornerRadius, 0, 0]);
ctx.roundRect(window.bounds.left, titleArea.top, Rect.width(window.bounds), Rect.height(titleArea), [cornerRadius, cornerRadius, 0, 0]);
ctx.fill()
ctx.roundRect(bounds.left, bounds.top, Rect.width(bounds), Rect.height(bounds), cornerRadius);
ctx.moveTo(bounds.left, bounds.top + headerHeight);
ctx.lineTo(bounds.right, bounds.top + headerHeight);
ctx.roundRect(window.bounds.left, titleArea.top, Rect.width(window.bounds), window.bounds.bottom - titleArea.top, cornerRadius);
ctx.moveTo(window.bounds.left, window.bounds.top);
ctx.lineTo(window.bounds.right, window.bounds.top);
ctx.stroke();
if (window.resizing) {
ctx.fillStyle = ctx.strokeStyle;
// diagonal lines in the bottom right
let offset = 6
let padding = 2
let count = 2;
ctx.beginPath()
for (let i=1; i<=count; i++) {
ctx.moveTo(window.bounds.right - padding, window.bounds.bottom - offset * i - padding);
ctx.lineTo(window.bounds.right - offset * i - padding, window.bounds.bottom - padding);
}
ctx.stroke();
}
}

export function drawWindowTitle(ctx: CanvasRenderingContext2D, titleBounds: Rect, title: string): number {
Expand All @@ -28,59 +41,115 @@ export function drawWindowTitle(ctx: CanvasRenderingContext2D, titleBounds: Rect
}

const titleHeight = 40
const resizeHandleSize = 20

export type WindowBounds = {
bounds: Rect
resizing: {
minWidth: number,
minHeight: number,
} | false
}

export function satisfyMinBounds(window: WindowBounds) {
if (window.resizing) {
if (Rect.width(window.bounds) < window.resizing.minWidth) {
window.bounds.right = window.bounds.left + window.resizing.minWidth
}
if (Rect.height(window.bounds) < window.resizing.minHeight) {
window.bounds.bottom = window.bounds.top + window.resizing.minHeight
}
}
}

// contains only input related data
export class WindowController implements InteractiveSystem {
export class WindowController<T extends WindowBounds> implements InteractiveSystem {
constructor(
protected contentArea: Rect,
protected drawContents: (ctx: CanvasRenderingContext2D, contentArea: Rect, titleArea: Rect) => unknown,
public windows: T[],
protected animateContents: (frame: AnimationFrame, window: T, titleArea: Rect) => unknown,
) {
}

dragState: null | {
lastX: number,
lastY: number,
pointerId: PointerId
window: T
mode: "move" | "resize"
} = null;

animate(frame: AnimationFrame): SleepState {
if (this.dragState) {
const mouse = ensured(frame.dragState.get(this.dragState.pointerId))
const dx = mouse.x - this.dragState.lastX
const dy = mouse.y - this.dragState.lastY
this.contentArea = Rect.addOffset(this.contentArea, dx, dy)
const window = this.dragState.window
if (this.dragState.mode === "resize") {
window.bounds.right += dx
window.bounds.bottom += dy
}
if (this.dragState.mode === "move") {
window.bounds = Rect.addOffset(window.bounds, dx, dy)
}
this.dragState.lastX = mouse.x
this.dragState.lastY = mouse.y
}
drawWindowFrame(frame.ctx, this.bounds, titleHeight);
this.drawContents(frame.ctx, this.contentArea, this.titleArea)
for (let window of this.windows) {
let titleArea = this.titleArea(window)
drawWindowFrame(frame.ctx, window, titleArea);
this.animateContents(frame, window, titleArea)
}
return "Sleeping"
}

get bounds(): Rect {
// including entire window frame
outerBounds(window: T): Rect {
let contentArea = window.bounds
return Rect.new(
this.contentArea.left, this.contentArea.top - titleHeight, this.contentArea.right, this.contentArea.bottom
contentArea.left, contentArea.top - titleHeight, contentArea.right, contentArea.bottom
)
}

get titleArea(): Rect {
// top area of window frame where title belongs and that can be used to drag the window
titleArea(window: T): Rect {
let outerBounds = this.outerBounds(window)
let contentArea = window.bounds
return Rect.new(
this.bounds.left, this.bounds.top, this.bounds.right, this.contentArea.top
outerBounds.left, outerBounds.top, outerBounds.right, contentArea.top
)
}

resize(newSize: Rect) {
this.contentArea = newSize
resizeArea(window: T): Rect {
let size = resizeHandleSize
return Rect.new(
window.bounds.right - size, window.bounds.bottom - size, window.bounds.right, window.bounds.bottom
)
}

onMouseDown(x: number, y: number, pointerId: PointerId): MouseDownResponse {
if (Rect.contains(this.titleArea, x, y)) {
this.dragState = {
lastX: x,
lastY: y,
pointerId
for (let window of this.windows) {
let titleArea = this.titleArea(window)
if (Rect.contains(titleArea, x, y)) {
this.dragState = {
lastX: x,
lastY: y,
pointerId,
mode: "move",
window,
}
return "Drag"
}
let resizeArea = this.resizeArea(window)
if (Rect.contains(resizeArea, x, y)) {
this.dragState = {
lastX: x,
lastY: y,
pointerId,
mode: "resize",
window,
}
return "Drag"
}
return "Drag"
}
return "Ignore"
}
Expand Down
Loading

0 comments on commit 28fa0da

Please sign in to comment.