From 27cf8cdac841f9be103fc7ffb505f3c653ad8ad1 Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Sun, 5 Sep 2021 21:06:15 +0200 Subject: [PATCH] feat: more/improved styles, moved settings --- src/Beat.ts | 2 +- src/ui/BeatGroup/Beat/Beat.css | 20 ------ src/ui/BeatGroup/Beat/BeatView.ts | 23 ------- .../BeatGroupSettingsView.ts | 40 +++++------- .../BeatLikeLoopSettings.css | 4 ++ .../BeatLikeLoopSettingsView.ts | 45 ++++++++------ src/ui/BeatSettings/BeatSettings.css | 9 +-- src/ui/BeatSettings/BeatSettingsView.ts | 14 ----- src/ui/Root/Root.css | 46 -------------- src/ui/Widgets/BoolBox/BoolBox.css | 61 +++++++++++++++++++ src/ui/Widgets/BoolBox/BoolBoxView.ts | 59 ++++++++++++++++++ src/ui/Widgets/NumberInput/NumberInput.css | 4 ++ src/ui/Widgets/NumberInput/NumberInputView.ts | 2 +- 13 files changed, 174 insertions(+), 155 deletions(-) create mode 100644 src/ui/Widgets/BoolBox/BoolBox.css create mode 100644 src/ui/Widgets/BoolBox/BoolBoxView.ts diff --git a/src/Beat.ts b/src/Beat.ts index 3475308..d3823ab 100644 --- a/src/Beat.ts +++ b/src/Beat.ts @@ -36,7 +36,7 @@ export default class Beat implements IPublisher, BeatLike { private looping: boolean; constructor(options?: BeatInitOptions) { - this.key = `Beat-${Beat.count}`; + this.key = `B-${Beat.count}`; this.name = options?.name ?? this.key; this.setTimeSignature({up: options?.timeSig?.up ?? 4, down: options?.timeSig?.down ?? 4}); this.setBarCount(options?.bars ?? 4); diff --git a/src/ui/BeatGroup/Beat/Beat.css b/src/ui/BeatGroup/Beat/Beat.css index ce9287c..0e74602 100644 --- a/src/ui/BeatGroup/Beat/Beat.css +++ b/src/ui/BeatGroup/Beat/Beat.css @@ -3,26 +3,6 @@ padding-left: 1em; } -.beat-settings-btn { - cursor: pointer; - line-height: 2em; - display: inline-block; - height: 2em; - border: 1px solid grey; - border-radius: 1em; - user-select: none; - transition: background-color 150ms; -} - -.beat-settings-btn:hover { - background-color: #eaeaea; -} - -.beat-settings-btn.active { - background-color: lightgrey; - transition: none; -} - .beat-unit-block { height: 2em; } diff --git a/src/ui/BeatGroup/Beat/BeatView.ts b/src/ui/BeatGroup/Beat/BeatView.ts index 68e27d4..5aa9400 100644 --- a/src/ui/BeatGroup/Beat/BeatView.ts +++ b/src/ui/BeatGroup/Beat/BeatView.ts @@ -4,7 +4,6 @@ import {IPublisher} from "../../../Publisher"; import ISubscriber from "../../../Subscriber"; import BeatUnitView from "./BeatUnit/BeatUnitView"; import "./Beat.css"; -import BeatSettingsView from "../../BeatSettings/BeatSettingsView"; export type BeatUINodeOptions = UINodeOptions & { beat: Beat, @@ -13,8 +12,6 @@ export type BeatUINodeOptions = UINodeOptions & { export default class BeatView extends UINode implements ISubscriber { private beat: Beat; private title!: HTMLHeadingElement; - private settingsView!: BeatSettingsView; - private settingsToggleButton!: HTMLDivElement; private beatUnitViews: BeatUnitView[] = []; private beatUnitViewBlock: HTMLElement | null = null; private lastHoveredBeatUnitView: BeatUnitView | null = null; @@ -45,15 +42,6 @@ export default class BeatView extends UINode implements ISubscriber { } } - private toggleSettings() { - this.settingsView.toggleVisible(); - if (this.settingsView.isOpen()) { - this.settingsToggleButton.classList.add("active"); - } else { - this.settingsToggleButton.classList.remove("active"); - } - } - private rebuildBeatUnitViews() { const beatUnitCount = this.beat.getBarCount() * this.beat.getTimeSigUp(); this.beatUnitViews.splice(beatUnitCount, this.beatUnitViews.length - beatUnitCount); @@ -148,12 +136,6 @@ export default class BeatView extends UINode implements ISubscriber { if (!this.beatUnitViewBlock) { throw new Error("Beat unit block setup failed!"); } - this.settingsView = new BeatSettingsView({beat: this.beat}); - this.settingsToggleButton = UINode.make("div", { - classes: ["beat-settings-btn"], - innerText: "Settings", - onclick: () => this.toggleSettings() - }); this.node = UINode.make("div", { classes: ["beat"], subs: [ @@ -164,11 +146,6 @@ export default class BeatView extends UINode implements ISubscriber { this.beatUnitViewBlock, ] }), - this.settingsToggleButton, - UINode.make("div", { - classes: ["beat-settings-container"], - subs: [this.settingsView.render()], - }), ], }); return this.node; diff --git a/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts b/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts index 2223c0e..9079ed1 100644 --- a/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts +++ b/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts @@ -6,6 +6,8 @@ import ISubscriber from "../../Subscriber"; import BeatGroup, {BeatGroupEvents} from "../../BeatGroup"; import {IPublisher} from "../../Publisher"; import {BeatEvents} from "../../Beat"; +import BoolBoxView from "../Widgets/BoolBox/BoolBoxView"; +import BeatSettingsView from "../BeatSettings/BeatSettingsView"; export type BeatGroupSettingsUINodeOptions = UINodeOptions & { beatGroup: BeatGroup, @@ -16,8 +18,8 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber private barCountInput!: NumberInputView; private timeSigUpInput!: NumberInputView; private loopSettingsView!: BeatLikeLoopSettingsView; - private autoBeatLengthCheckbox!: HTMLInputElement; - private forceFullBarsCheckbox!: HTMLInputElement; + private autoBeatLengthCheckbox!: BoolBoxView; + private forceFullBarsCheckbox!: BoolBoxView; private autoBeatOptions!: HTMLElement; constructor(options: BeatGroupSettingsUINodeOptions) { @@ -58,19 +60,10 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber setter: (input: number) => this.beatGroup.setTimeSigUp(input), getter: () => this.beatGroup.getTimeSigUp(), }); - this.autoBeatLengthCheckbox = UINode.make("input", { - type: "checkbox", - checked: this.beatGroup.autoBeatLengthOn(), - oninput: () => { - this.beatGroup.setIsUsingAutoBeatLength(this.autoBeatLengthCheckbox.checked); - }, - }); - this.forceFullBarsCheckbox = UINode.make("input", { - type: "checkbox", - checked: this.beatGroup.forcesFullBars(), - oninput: () => { - this.beatGroup.setForcesFullBars(this.forceFullBarsCheckbox.checked); - }, + this.autoBeatLengthCheckbox = new BoolBoxView({ + label: "Auto beat length:", + value: this.beatGroup.autoBeatLengthOn(), + onInput: (isChecked: boolean) => this.beatGroup.setIsUsingAutoBeatLength(isChecked), }); this.autoBeatOptions = UINode.make("div", { classes: ["beat-group-settings-option-group"], @@ -78,19 +71,15 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber UINode.make("div", { classes: ["beat-group-settings-autobeat-option", "beat-group-settings-option"], subs: [ - UINode.make("label", { innerText: "Auto beat length:"}), - this.autoBeatLengthCheckbox, - ], - }), - UINode.make("div", { - classes: ["beat-group-settings-autobeat-option", "beat-group-settings-option"], - subs: [ - UINode.make("label", { innerText: "Force full bars:"}), - this.forceFullBarsCheckbox, + this.autoBeatLengthCheckbox.render(), ], }), ] }); + const beatSettingsViews = []; + for (let i = 0; i < this.beatGroup.getBeatCount(); i++) { + beatSettingsViews.push(new BeatSettingsView({ beat: this.beatGroup.getBeatByIndex(i) })); + } this.node = UINode.make("div", { classes: ["beat-group-settings"], subs: [ @@ -114,7 +103,8 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber UINode.make("button", { innerText: "New Track", onclick: () => this.beatGroup.addBeat(), - }) + }), + ...beatSettingsViews.map(view => view.render()), ], }), ], diff --git a/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettings.css b/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettings.css index 29277e2..984e757 100644 --- a/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettings.css +++ b/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettings.css @@ -19,4 +19,8 @@ .loop-settings-option { display: flex; justify-content: center; +} + +.loop-settings-option.hide { + display: none; } \ No newline at end of file diff --git a/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts b/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts index 2d1e0fe..d4fae5f 100644 --- a/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts +++ b/src/ui/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts @@ -5,6 +5,7 @@ import ISubscriber from "../../Subscriber"; import UINode, {UINodeOptions} from "../UINode"; import {BeatEvents} from "../../Beat"; import {IPublisher} from "../../Publisher"; +import BoolBoxView from "../Widgets/BoolBox/BoolBoxView"; export type BeatLikeLoopSettingsViewUINodeOptions = UINodeOptions & { beatLike: BeatLike, @@ -13,7 +14,8 @@ export type BeatLikeLoopSettingsViewUINodeOptions = UINodeOptions & { export default class BeatLikeLoopSettingsView extends UINode implements ISubscriber { private beatLike: BeatLike; private loopLengthInput!: NumberInputView; - private loopCheckbox!: HTMLInputElement; + private loopCheckbox!: BoolBoxView; + private loopLengthSection!: HTMLDivElement; constructor(options: BeatLikeLoopSettingsViewUINodeOptions) { super(options); @@ -32,7 +34,12 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri if (event === BeatEvents.LoopLengthChanged) { this.loopLengthInput.setValue(this.beatLike.getLoopLength()); } else if (event === BeatEvents.DisplayTypeChanged) { - this.loopCheckbox.checked = this.beatLike.isLooping(); + this.loopCheckbox.setValue(this.beatLike.isLooping()); + if (this.beatLike.isLooping()) { + this.loopLengthSection.classList.remove("hide"); + } else { + this.loopLengthSection.classList.add("hide"); + } } } @@ -44,34 +51,36 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri onIncrement: () => this.beatLike.setLoopLength(this.beatLike.getLoopLength() + 1), onNewInput: (input: number) => this.beatLike.setLoopLength(input), }); - this.loopCheckbox = UINode.make("input", { - classes: ["loop-settings-loop-toggle"], - type: "checkbox", - checked: this.beatLike.isLooping(), - oninput: (event: Event) => { - this.beatLike.setLooping((event.target as HTMLInputElement).checked); - }, + this.loopCheckbox = new BoolBoxView({ + label: "On:", + value: this.beatLike.isLooping(), + onInput: (isChecked: boolean) => this.beatLike.setLooping(isChecked), }); + this.loopLengthSection = UINode.make("div", { + classes: ["loop-settings-option"], + subs: [ + this.loopLengthInput.render(), + ], + }); + if (this.beatLike.isLooping()) { + this.loopLengthSection.classList.remove("hide"); + } else { + this.loopLengthSection.classList.add("hide"); + } this.node = UINode.make("div", { classes: ["loop-settings"], subs: [ - UINode.make("p", {innerText: "Looping:"}), + UINode.make("p", {innerText: "Global looping settings:"}), UINode.make("div", { classes: ["loop-settings-option-group"], subs: [ UINode.make("div", { classes: ["loop-settings-option"], subs: [ - this.loopLengthInput.render(), - ], - }), - UINode.make("div", { - classes: ["loop-settings-option"], - subs: [ - UINode.make("label", {innerText: "On:"}), - this.loopCheckbox, + this.loopCheckbox.render(), ], }), + this.loopLengthSection, ], }), ] diff --git a/src/ui/BeatSettings/BeatSettings.css b/src/ui/BeatSettings/BeatSettings.css index e343aa5..d9eeadd 100644 --- a/src/ui/BeatSettings/BeatSettings.css +++ b/src/ui/BeatSettings/BeatSettings.css @@ -1,13 +1,8 @@ .beat-settings { padding: 1em; - display: none; - text-align: center; - width: 40em; - justify-content: space-evenly; -} - -.beat-settings.visible { display: inline-flex; + text-align: center; + justify-content: space-evenly; } .beat-settings-time-sig-up { diff --git a/src/ui/BeatSettings/BeatSettingsView.ts b/src/ui/BeatSettings/BeatSettingsView.ts index 8fd6155..854566a 100644 --- a/src/ui/BeatSettings/BeatSettingsView.ts +++ b/src/ui/BeatSettings/BeatSettingsView.ts @@ -2,7 +2,6 @@ import "./BeatSettings.css"; import Beat, {BeatEvents} from "../../Beat"; import UINode, {UINodeOptions} from "../UINode"; import ISubscriber from "../../Subscriber"; -import NumberInputView from "../Widgets/NumberInput/NumberInputView"; import BeatLikeLoopSettingsView from "../BeatLikeLoopSettings/BeatLikeLoopSettingsView"; import {IPublisher} from "../../Publisher"; @@ -32,19 +31,6 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } } - toggleVisible(): void { - this.visible = !this.visible; - if (this.visible) { - this.node?.classList.add("visible"); - } else { - this.node?.classList.remove("visible"); - } - } - - isOpen(): boolean { - return this.visible; - } - rebuild(): HTMLElement { this.loopSettingsView = new BeatLikeLoopSettingsView({beatLike: this.beat}); this.nameInput = UINode.make("input", { diff --git a/src/ui/Root/Root.css b/src/ui/Root/Root.css index c2bca09..850246d 100644 --- a/src/ui/Root/Root.css +++ b/src/ui/Root/Root.css @@ -49,52 +49,6 @@ margin: auto; } -input[type="checkbox"] { - position: relative; - width: 2em; - height: 1em; - padding: 0; - margin: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -input[type="checkbox"]::before { - width: 2em; - height: 1em; - border-radius: 1em; - background-color: var(--color-ui-accent-dark); - display: block; - content: ""; - z-index: 0; - position: absolute; - transition: background-color 200ms; -} - -input[type="checkbox"]:checked::before { - background-color: var(--color-ui-accent-light); -} - -input[type="checkbox"]::after { - position: absolute; - width: 1em; - height: 1em; - border-radius: 1em; - border-color: var(--color-ui-neutral-dark); - border-width: 1px; - background-color: var(--color-ui-neutral-light); - display: block; - content: ""; - z-index: 1; - right: 1em; - transition: right 200ms; -} - -input[type="checkbox"]:checked::after { - right: 0; -} - * { user-drag: none; user-select: none; diff --git a/src/ui/Widgets/BoolBox/BoolBox.css b/src/ui/Widgets/BoolBox/BoolBox.css new file mode 100644 index 0000000..5c51ceb --- /dev/null +++ b/src/ui/Widgets/BoolBox/BoolBox.css @@ -0,0 +1,61 @@ +.bool-box { + height: 1.1em; + margin: 0.5em; + line-height: 1em; +} + +.bool-box-label { + display: inline-block; + margin-right: 0.5em; +} + +input.bool-box-checkbox[type="checkbox"] { + position: relative; + display: inline-block; + width: 2em; + height: 1em; + padding: 0; + top: 0.1em; + margin: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +input.bool-box-checkbox[type="checkbox"]::before { + width: 2em; + height: 1em; + border-radius: 1em; + background-color: var(--color-ui-accent-dark); + display: inline-block; + content: ""; + z-index: 0; + position: absolute; + transition: background-color 200ms; +} + +input.bool-box-checkbox[type="checkbox"]:checked::before { + background-color: var(--color-ui-accent-light); +} + +input.bool-box-checkbox[type="checkbox"]::after { + position: absolute; + width: 1.05em; + height: 1.05em; + border-radius: 1em; + border-color: var(--color-ui-neutral-dark); + border-width: 0.05em; + border-style: solid; + background-color: var(--color-ui-neutral-dark); + display: block; + content: ""; + top: -0.075em; + z-index: 1; + left: -0.075em; + transition: left 200ms, background-color 200ms; +} + +input.bool-box-checkbox[type="checkbox"]:checked::after { + left: 0.925em; + background-color: var(--color-ui-neutral-light); +} \ No newline at end of file diff --git a/src/ui/Widgets/BoolBox/BoolBoxView.ts b/src/ui/Widgets/BoolBox/BoolBoxView.ts new file mode 100644 index 0000000..99330af --- /dev/null +++ b/src/ui/Widgets/BoolBox/BoolBoxView.ts @@ -0,0 +1,59 @@ +import "./BoolBox.css"; +import UINode, {UINodeOptions} from "../../UINode"; + +export type BoolBoxUINodeOptions = UINodeOptions & { + label?: string, + value?: boolean, + onInput?: (isChecked: boolean) => void, +}; + +export default class BoolBoxView extends UINode { + private label: string | null; + private labelElement!: HTMLLabelElement; + private checkboxElement!: HTMLInputElement; + private onInput: (isChecked: boolean) => void; + + constructor(options: BoolBoxUINodeOptions) { + super(options); + this.label = options.label ?? ""; + this.onInput = options.onInput ?? (() => { /* dummy */ }); + } + + setLabel(newLabel: string | null): void { + if (newLabel !== null) { + this.label = newLabel; + this.labelElement.innerText = newLabel; + this.labelElement.classList.add("visible"); + } else { + this.label = newLabel; + this.labelElement.innerText = ""; + this.labelElement.classList.remove("visible"); + } + } + + setValue(isChecked: boolean): void { + this.checkboxElement.checked = isChecked; + } + + rebuild(): HTMLDivElement { + this.labelElement = UINode.make("label", { + classes: ["bool-box-label"], + innerText: this.label ?? "", + }); + if (this.label !== null) { + this.labelElement.classList.add("visible"); + } + this.checkboxElement = UINode.make("input", { + type: "checkbox", + classes: ["bool-box-checkbox"], + oninput: (event: Event) => this.onInput((event.target as HTMLInputElement).checked), + }); + return UINode.make("div", { + classes: ["bool-box"], + subs: [ + this.labelElement, + this.checkboxElement, + ], + }); + } +} \ No newline at end of file diff --git a/src/ui/Widgets/NumberInput/NumberInput.css b/src/ui/Widgets/NumberInput/NumberInput.css index 8211f34..89111ff 100644 --- a/src/ui/Widgets/NumberInput/NumberInput.css +++ b/src/ui/Widgets/NumberInput/NumberInput.css @@ -2,6 +2,10 @@ text-align: center; } +.number-input-label { + margin-bottom: 0.5em; +} + input[type="number"].number-input-input { -webkit-appearance: textfield; -moz-appearance: textfield; diff --git a/src/ui/Widgets/NumberInput/NumberInputView.ts b/src/ui/Widgets/NumberInput/NumberInputView.ts index 6bd2040..2e53804 100644 --- a/src/ui/Widgets/NumberInput/NumberInputView.ts +++ b/src/ui/Widgets/NumberInput/NumberInputView.ts @@ -76,7 +76,7 @@ export default class NumberInputView extends UINode { type: "number", classes: ["number-input-input"], valueAsNumber: this.value, - oninput: (event: Event) => { + onblur: (event: Event) => { const input = (event.target as HTMLInputElement).valueAsNumber; if (!isNaN(input)) { if (this.onNewInput) {