UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

211 lines 7.66 kB
import { ButtonClickHelper } from '../helpers/ButtonClickHelper.js'; import { Widget } from './Widget.js'; import { ClickState } from '../helpers/ClickState.js'; import { paintCircle } from '../helpers/paintCircle.js'; import { PropagationModel } from '../events/WidgetEvent.js'; import { FocusEvent } from '../events/FocusEvent.js'; import { BlurEvent } from '../events/BlurEvent.js'; /** * A radio button widget; used for selecting one of many options. Uses a shared * {@link Box} instance and expects the creation of multiple RadioButton * instances. * * @typeParam V - The type stored in the {@link RadioButton#"variable"}; when a radio button is clicked, the value inside the variable has this type. * * @category Widget */ export class RadioButton extends Widget { /** * @param variable - The shared variable that radio buttons will save the value to when selected. * @param value - The value that will be used to set the {@link RadioButton#"variable"} when the radio button is clicked */ constructor(variable, value, properties) { var _a; // Radio buttons need a clear background, have no children and don't // propagate events super(properties); /** Horizontal offset. */ this.offsetX = 0; /** Vertical offset. */ this.offsetY = 0; /** Actual length after resolving layout. */ this.actualLength = 0; /** Was the radio button selected in the last paint? */ this._wasSelected = false; this._clickable = (_a = properties === null || properties === void 0 ? void 0 : properties.clickable) !== null && _a !== void 0 ? _a : true; this.tabFocusable = true; this.variable = variable; this.value = value; this.clickHelper = new ButtonClickHelper(this); this.callback = this.handleChange.bind(this); this._wasSelected = this.selected; } handleChange() { if (this.selected !== this._wasSelected) { this.markWholeAsDirty(); } } onThemeUpdated(property = null) { super.onThemeUpdated(property); if (property === null || property === 'radioButtonLength') { this._layoutDirty = true; this.markWholeAsDirty(); } else if (property === 'backgroundGlowFill' || property === 'backgroundFill' || property === 'accentFill' || property === 'primaryFill' || property === 'radioButtonInnerPadding') { this.markWholeAsDirty(); } } /** * Select this radio button. Sets the value in * {@link RadioButton#"variable"} to be {@link RadioButton#value} */ select() { this.variable.value = this.value; } /** * Is the radio button selected? Equivalent to checking if the value in the * {@link RadioButton#"variable"} is strictly equal to the * {@link RadioButton#value} */ get selected() { return this.variable.value === this.value; } handleEvent(event) { if (event.propagation !== PropagationModel.Trickling) { if (event.isa(FocusEvent)) { if (this.clickHelper.onFocusGrabbed(event.focusType)) { this.markWholeAsDirty(); } return this; } else if (event.isa(BlurEvent)) { if (this.clickHelper.onFocusDropped(event.focusType)) { this.markWholeAsDirty(); } return this; } else { return super.handleEvent(event); } } const x = this.idealX + this.offsetX; const y = this.idealY + this.offsetY; const [wasClick, capture] = this.clickHelper.handleEvent(event, this.root, this._clickable, [x, x + this.actualLength, y, y + this.actualLength]); // Select radio button if button was clicked if (wasClick) { this.select(); } // Always mark as dirty if the click state changed (so glow colour takes // effect). Toggle value if clicked if (this.clickHelper.clickStateChanged) { this.markWholeAsDirty(); } return capture ? this : null; } handleResolveDimensions(minWidth, maxWidth, minHeight, maxHeight) { // Resolve width and height const minLength = Math.min(this.radioButtonLength, maxWidth, maxHeight); this.idealWidth = minLength; this.idealHeight = minLength; if (this.idealWidth < minWidth) { this.idealWidth = minWidth; } if (this.idealHeight < minHeight) { this.idealHeight = minHeight; } } finalizeBounds() { super.finalizeBounds(); // Center checkbox this.actualLength = Math.min(this.radioButtonLength, this.width, this.height); this.offsetX = (this.width - this.actualLength) / 2; this.offsetY = (this.height - this.actualLength) / 2; } handlePainting(_dirtyRects) { this._wasSelected = this.selected; // Should we use glow colours? (background glow and accent) const useGlow = this.clickHelper.clickState === ClickState.Hover || this.clickHelper.clickState === ClickState.Hold; // Draw unchecked part of radio button const ctx = this.viewport.context; if (useGlow) { ctx.fillStyle = this.backgroundGlowFill; } else { ctx.fillStyle = this.backgroundFill; } const halfLength = this.actualLength / 2; const radioX = this.offsetX + this.x + halfLength; const radioY = this.offsetY + this.y + halfLength; paintCircle(ctx, radioX, radioY, halfLength); // Draw checked part of checkbox if (this.selected) { if (useGlow) { ctx.fillStyle = this.accentFill; } else { ctx.fillStyle = this.primaryFill; } const innerLength = this.actualLength - this.radioButtonInnerPadding * 2; // Fall back to filling entire radio button if there isn't enough // space for padding if (innerLength <= 0) { paintCircle(ctx, radioX, radioY, halfLength); } else { const halfInnerLength = innerLength / 2; paintCircle(ctx, radioX, radioY, halfInnerLength); } } } attach(root, viewport, parent) { super.attach(root, viewport, parent); this.variable.watch(this.callback); } detach() { super.detach(); this.variable.unwatch(this.callback); } activate() { super.activate(); this.clickHelper.reset(); } /** * Is the radio button clickable? True by default. Used for disabling the * radio button without hiding it. */ get clickable() { return this._clickable; } set clickable(clickable) { if (this._clickable === clickable) { return; } this._clickable = clickable; this.clickHelper.reset(); this.markWholeAsDirty(); } handlePreLayoutUpdate() { super.handlePreLayoutUpdate(); this.clickHelper.doneProcessing(); } } RadioButton.autoXML = { name: 'radio-button', inputConfig: [ { mode: 'value', name: 'variable', validator: 'box' }, { mode: 'value', name: 'value' } ] }; //# sourceMappingURL=RadioButton.js.map