feat: more/improved styles, moved settings
This commit is contained in:
@@ -36,7 +36,7 @@ export default class Beat implements IPublisher<BeatEvents>, 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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()),
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -20,3 +20,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loop-settings-option.hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -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,
|
||||
],
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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;
|
||||
|
||||
61
src/ui/Widgets/BoolBox/BoolBox.css
Normal file
61
src/ui/Widgets/BoolBox/BoolBox.css
Normal file
@@ -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);
|
||||
}
|
||||
59
src/ui/Widgets/BoolBox/BoolBoxView.ts
Normal file
59
src/ui/Widgets/BoolBox/BoolBoxView.ts
Normal file
@@ -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,
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user