From cc189d6f643c798a9d2a46f325ee094206e541c8 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Wed, 27 Nov 2024 15:19:36 -0500 Subject: [PATCH] feat(ags osk): allow adding borders to arc --- nixosModules/ags/config/lib.ts | 48 ++++++ .../widgets/on-screen-keyboard/arcs.tsx | 137 ++++++++++++++---- 2 files changed, 155 insertions(+), 30 deletions(-) diff --git a/nixosModules/ags/config/lib.ts b/nixosModules/ags/config/lib.ts index 3d50c792..c9edfeb0 100644 --- a/nixosModules/ags/config/lib.ts +++ b/nixosModules/ags/config/lib.ts @@ -29,6 +29,54 @@ export interface CursorPos { y: number } +type PointProps = [number, number] | { + x: number + y: number +} | number; + +export class Point { + public x = 0; + public y = 0; + + get values(): [number, number] { + return [this.x, this.y]; + } + + constructor(props?: PointProps, y?: number) { + if (typeof props === 'number') { + if (y) { + this.x = props; + this.y = y; + } + else { + throw new Error('Wrong props'); + } + } + else if (Array.isArray(props)) { + this.x = props[0]; + this.y = props[1]; + } + else if (props) { + this.x = props.x; + this.y = props.y; + } + } +} + +export type BezierPoints = [number, number, number, number]; + +export class Bezier { + private _points: BezierPoints; + + get points() { + return [...this._points] as BezierPoints; + } + + constructor(x1: number, y1: number, x2: number, y2: number) { + this._points = [x1, y1, x2, y2]; + } +} + export const get_hyprland_monitor = (monitor: Gdk.Monitor): AstalHyprland.Monitor | undefined => { const hyprland = AstalHyprland.get_default(); diff --git a/nixosModules/ags/config/widgets/on-screen-keyboard/arcs.tsx b/nixosModules/ags/config/widgets/on-screen-keyboard/arcs.tsx index fec1399c..924ae70f 100644 --- a/nixosModules/ags/config/widgets/on-screen-keyboard/arcs.tsx +++ b/nixosModules/ags/config/widgets/on-screen-keyboard/arcs.tsx @@ -1,12 +1,14 @@ -import { Gtk } from 'astal/gtk3'; +import { Gdk, Gtk } from 'astal/gtk3'; + import Cairo from 'cairo'; +import { Bezier, BezierPoints, Point } from '../../lib'; + /* Types */ -interface Point { - x: number - y: number -} -type Bezier = [number, number, number, number]; +type Side = 'top' | 'right' | 'bottom' | 'left'; + + +const SIDES: Side[] = ['top', 'right', 'bottom', 'left']; export default ({ css = 'background-color: black;', @@ -19,47 +21,115 @@ export default ({ setup={(widget) => { widget.set_size_request(allocation.width, allocation.height); - const styleContext = widget.get_style_context(); - const bgColor = styleContext.get_background_color(Gtk.StateFlags.NORMAL); - widget.connect('draw', (_, cairoContext: Cairo.Context) => { - const drawBezier = (dest: Point, curve: Bezier) => { - curve[0] *= (dest.x - cairoContext.getCurrentPoint()[0]); - curve[0] += cairoContext.getCurrentPoint()[0]; + widget.set_size_request(allocation.width, allocation.height); - curve[1] *= (dest.y - cairoContext.getCurrentPoint()[1]); - curve[1] += cairoContext.getCurrentPoint()[1]; + const styleContext = widget.get_style_context(); - curve[2] *= (dest.x - cairoContext.getCurrentPoint()[0]); - curve[2] += cairoContext.getCurrentPoint()[0]; + const bgColor = styleContext.get_background_color(Gtk.StateFlags.NORMAL); - curve[3] *= (dest.y - cairoContext.getCurrentPoint()[1]); - curve[3] += cairoContext.getCurrentPoint()[1]; + const borderWidth = styleContext.get_border(Gtk.StateFlags.NORMAL); + + const borderColor = Object.fromEntries(SIDES.map((side) => [ + side, + styleContext.get_property( + `border-${side}-color`, Gtk.StateFlags.NORMAL, + ) as Gdk.RGBA, + ])) as Record; + + /* + * Draws a cubic bezier curve from the current point + * of the cairo context. + * + * @param dest the destination point of the curve + * @param curve the cubic bezier's control points. this + * is the same as a CSS easing function + */ + const drawBezier = (dest: Point, curve: BezierPoints): void => { + const start = new Point(cairoContext.getCurrentPoint()); + + // Converts the ratios to absolute coordinates + curve[0] = curve[0] * (dest.x - start.x) + start.x; + curve[1] = curve[1] * (dest.y - start.y) + start.y; + curve[2] = curve[2] * (dest.x - start.x) + start.x; + curve[3] = curve[3] * (dest.y - start.y) + start.y; cairoContext.curveTo(...curve, dest.x, dest.y); }; + const colorBorder = ( + side: Side, + start: Point, + dest: Point, + curve?: BezierPoints, + ) => { + cairoContext.moveTo(...start.values); + + if (curve) { + drawBezier(dest, curve); + } + else { + cairoContext.lineTo(...dest.values); + } + + cairoContext.setLineWidth(borderWidth[side]); + cairoContext.setSourceRGBA( + borderColor[side].red, + borderColor[side].green, + borderColor[side].blue, + borderColor[side].alpha, + ); + + cairoContext.stroke(); + }; + + const bottomLeft = new Point({ + x: 0, + y: allocation.height, + }); + + const topLeft = new Point({ + x: 0, + y: 0, + }); + + const middleLeft = new Point({ + x: allocation.width / 3, + y: allocation.height * 0.8, + }); + + const middleRight = new Point({ + x: (allocation.width / 3) * 2, + y: allocation.height * 0.8, + }); + + const topRight = new Point({ + x: allocation.width, + y: 0, + }); + + const bottomRight = new Point({ + x: allocation.width, + y: allocation.height, + }); + + const bezier = new Bezier(0.76, 0, 0.24, 1); + // bottom left to top left - cairoContext.moveTo(0, allocation.height); - cairoContext.lineTo(0, 0); + cairoContext.moveTo(...bottomLeft.values); + cairoContext.lineTo(...topLeft.values); // top left to middle left - drawBezier( - { x: allocation.width / 3, y: allocation.height * 0.8 }, - [0.76, 0, 0.24, 1], - ); + drawBezier(middleLeft, bezier.points); // middle left to middle right - cairoContext.lineTo((allocation.width / 3) * 2, allocation.height * 0.8); + cairoContext.lineTo(...middleRight.values); // middle right to top right - drawBezier( - { x: allocation.width, y: 0 }, - [0.76, 0, 0.24, 1], - ); + drawBezier(topRight, bezier.points); // top right to bottom right - cairoContext.lineTo(allocation.width, allocation.height); + cairoContext.lineTo(...bottomRight.values); // bottom right to bottom left cairoContext.closePath(); @@ -67,6 +137,13 @@ export default ({ // Add color cairoContext.setSourceRGBA(bgColor.red, bgColor.green, bgColor.blue, bgColor.alpha); cairoContext.fill(); + + colorBorder('left', bottomLeft, topLeft); + colorBorder('top', topLeft, middleLeft, bezier.points); + colorBorder('top', middleLeft, middleRight); + colorBorder('top', middleRight, topRight, bezier.points); + colorBorder('right', topRight, bottomRight); + colorBorder('bottom', bottomLeft, bottomRight); }); }} />