half done with the widget system

This commit is contained in:
ramollia
2025-10-31 17:33:19 +01:00
parent b84255e00e
commit 1c9af58264
30 changed files with 1009 additions and 244 deletions

View File

@@ -1,11 +1,10 @@
package org.toop.app; package org.toop.app;
import javafx.geometry.Pos; import org.toop.app.widget.Widget;
import org.toop.app.view.ViewStack;
import org.toop.app.view.views.QuitView;
import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.ConfirmWidget; import org.toop.app.widget.display.SongDisplay;
import org.toop.app.widget.complex.PopupWidget; import org.toop.app.widget.popup.QuitPopup;
import org.toop.app.widget.primary.MainPrimary;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
@@ -14,12 +13,11 @@ import org.toop.local.AppContext;
import org.toop.local.AppSettings; import org.toop.local.AppSettings;
import javafx.application.Application; import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.HashMap;
public final class App extends Application { public final class App extends Application {
private static Stage stage; private static Stage stage;
private static Scene scene; private static Scene scene;
@@ -39,6 +37,8 @@ public final class App extends Application {
final Scene scene = new Scene(root); final Scene scene = new Scene(root);
stage.setTitle(AppContext.getString("app-title")); stage.setTitle(AppContext.getString("app-title"));
stage.titleProperty().bind(AppContext.bindToKey("app-title"));
stage.setWidth(1080); stage.setWidth(1080);
stage.setHeight(720); stage.setHeight(720);
@@ -65,21 +65,8 @@ public final class App extends Application {
AppSettings.applySettings(); AppSettings.applySettings();
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
var abc = new ConfirmWidget("abc"); WidgetContainer.add(Pos.CENTER, new MainPrimary());
var cab = new ConfirmWidget("cab"); WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay());
abc.addButton("test", () -> {
abc.replace(cab, Pos.CENTER);
});
abc.addButton("test3333", () -> IO.println("Second test works!"));
cab.addButton("cab321312", () -> IO.println("Third test"));
cab.addButton("cab31232132131", () -> {
IO.println("Fourth test");
});
WidgetContainer.add(Pos.CENTER, abc);
} }
public static void startQuit() { public static void startQuit() {
@@ -87,47 +74,32 @@ public final class App extends Application {
return; return;
} }
ViewStack.push(new QuitView()); WidgetContainer.add(Pos.CENTER, new QuitPopup());
isQuitting = true; isQuitting = true;
} }
public static void stopQuit() { public static void stopQuit() {
ViewStack.pop();
isQuitting = false; isQuitting = false;
} }
public static void quit() { public static void quit() {
ViewStack.cleanup();
stage.close(); stage.close();
System.exit(0); // TODO: This is like dropping a nuke System.exit(0); // TODO: This is like dropping a nuke
} }
public static void reload() {
stage.setTitle(AppContext.getString("app-title"));
//ViewStack.reload();
}
public static void setFullscreen(boolean fullscreen) { public static void setFullscreen(boolean fullscreen) {
stage.setFullScreen(fullscreen); stage.setFullScreen(fullscreen);
width = (int) stage.getWidth(); width = (int)stage.getWidth();
height = (int) stage.getHeight(); height = (int)stage.getHeight();
reload();
} }
public static void setStyle(String theme, String layoutSize) { public static void setStyle(String theme, String layoutSize) {
final int stylesCount = scene.getStylesheets().size(); scene.getStylesheets().clear();
for (int i = 0; i < stylesCount; i++) {
scene.getStylesheets().removeLast();
}
scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl()); scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl()); scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl()); scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
reload();
} }
public static int getWidth() { public static int getWidth() {

View File

@@ -1,14 +0,0 @@
//package org.toop.app;
//
//public class Quit {
// PopupWidget popup;
// Quit() {
// this.popup = new PopupWidget(
// new ConfirmationWidget(
// "are-you-sure",
// "yes", () -> App.quit(),
// "no", () -> popup.pop()
// )
// );
// }
//}

View File

@@ -1,6 +0,0 @@
package org.toop.app.interfaces;
public interface Popup {
void push();
void pop();
}

View File

@@ -1,11 +1,13 @@
package org.toop.app.view.displays; package org.toop.app.view.displays;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.toop.app.widget.Widget;
import org.toop.framework.audio.AudioEventListener; import org.toop.framework.audio.AudioEventListener;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
@@ -13,7 +15,7 @@ import javafx.geometry.Pos;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import org.toop.framework.eventbus.GlobalEventBus; import org.toop.framework.eventbus.GlobalEventBus;
public class SongDisplay extends VBox { public class SongDisplay extends VBox implements Widget {
private final Text songTitle; private final Text songTitle;
private final ProgressBar progressBar; private final ProgressBar progressBar;
@@ -107,6 +109,11 @@ public class SongDisplay extends VBox {
String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr; String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr;
return time; return time;
} }
@Override
public Node getNode() {
return this;
}
} }

View File

@@ -126,7 +126,6 @@ public final class OptionsView extends View {
languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setLocale(newValue.toString()); AppSettings.getSettings().setLocale(newValue.toString());
AppContext.setLocale(newValue); AppContext.setLocale(newValue);
App.reload();
}); });
languageCombobox.setConverter(new StringConverter<>() { languageCombobox.setConverter(new StringConverter<>() {

View File

@@ -1,63 +1,150 @@
package org.toop.app.widget; package org.toop.app.widget;
import org.toop.local.AppContext;
import java.util.function.Consumer;
import javafx.collections.FXCollections;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.util.StringConverter;
public final class Primitive { public final class Primitive {
public static Text header(String label) { public static Text header(String key) {
var header = new Text(label); var header = new Text();
header.getStyleClass().add("header"); header.getStyleClass().add("header");
return header;
}
public static Text text(String label) { header.setText(AppContext.getString(key));
var text = new Text(label); header.textProperty().bind(AppContext.bindToKey(key));
text.getStyleClass().add("text");
return text;
}
public static Button button(String label) { return header;
var button = new Button(label); }
button.getStyleClass().add("button");
return button;
}
public static TextField input() { public static Text text(String key) {
var input = new TextField(); var text = new Text();
input.getStyleClass().add("input"); text.getStyleClass().add("text");
return input;
}
public static Slider slider() { text.setText(AppContext.getString(key));
var slider = new Slider(); text.textProperty().bind(AppContext.bindToKey(key));
slider.getStyleClass().add("slider");
return slider;
}
public static <T> ComboBox<T> choice() { return text;
var choice = new ComboBox<T>(); }
choice.getStyleClass().add("choice");
return choice;
}
public static ScrollPane scroll(Node content) { public static Button button(String key, Runnable onAction) {
var scroll = new ScrollPane(content); var button = new Button();
scroll.getStyleClass().add("scroll"); button.getStyleClass().add("button");
return scroll;
}
public static HBox hbox(Node... nodes) { button.setText(AppContext.getString(key));
var hbox = new HBox(nodes); button.textProperty().bind(AppContext.bindToKey(key));
hbox.getStyleClass().add("container");
return hbox;
}
public static VBox vbox(Node... nodes) { if (onAction != null) {
var vbox = new VBox(nodes); button.setOnAction(_ ->
vbox.getStyleClass().add("container"); onAction.run());
return vbox; }
}
return button;
}
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged) {
var input = new TextField();
input.getStyleClass().add("input");
input.setPromptText(AppContext.getString(promptKey));
input.promptTextProperty().bind(AppContext.bindToKey(promptKey));
input.setText(text);
if (onValueChanged != null) {
input.textProperty().addListener((_, _, newValue) ->
onValueChanged.accept(newValue));
}
return input;
}
public static Slider slider(int min, int max, int value, Consumer<Integer> onValueChanged) {
var slider = new Slider();
slider.getStyleClass().add("slider");
slider.setMin(min);
slider.setMax(max);
slider.setValue(value);
if (onValueChanged != null) {
slider.valueProperty().addListener((_, _, newValue) ->
onValueChanged.accept(newValue.intValue()));
}
return slider;
}
@SafeVarargs
public static <T> ComboBox<T> choice(StringConverter<T> converter, T value, Consumer<T> onValueChanged, T... items) {
var choice = new ComboBox<T>();
choice.getStyleClass().add("choice");
if (converter != null) {
choice.setConverter(converter);
}
if (value != null) {
choice.setValue(value);
}
if (onValueChanged != null) {
choice.valueProperty().addListener((_, _, newValue) ->
onValueChanged.accept(newValue));
}
choice.setItems(FXCollections.observableArrayList(items));
return choice;
}
public static ScrollPane scroll(Node content) {
var scroll = new ScrollPane();
scroll.getStyleClass().add("scroll");
scroll.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
scroll.setFitToWidth(true);
scroll.setContent(content);
return scroll;
}
public static Separator separator() {
var separator = new Separator();
separator.getStyleClass().add("separator");
return separator;
}
public static HBox hbox(Node... nodes) {
var hbox = new HBox();
hbox.getStyleClass().add("container");
hbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
hbox.getChildren().addAll(nodes);
return hbox;
}
public static VBox vbox(Node... nodes) {
var vbox = new VBox();
vbox.getStyleClass().add("container");
vbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
vbox.getChildren().addAll(nodes);
return vbox;
}
} }

View File

@@ -3,8 +3,8 @@ package org.toop.app.widget;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
public interface Widget<T extends Node> { public interface Widget {
T getNode(); Node getNode();
default void show(Pos position) { default void show(Pos position) {
WidgetContainer.add(position, this); WidgetContainer.add(position, this);
@@ -14,8 +14,8 @@ public interface Widget<T extends Node> {
WidgetContainer.remove(this); WidgetContainer.remove(this);
} }
default void replace(Widget<?> newWidget, Pos newWidgetPosition) { default void replace(Pos position, Widget widget) {
this.hide(); widget.show(position);
newWidget.show(newWidgetPosition); hide();
} }
} }

View File

@@ -1,12 +1,21 @@
package org.toop.app.widget; package org.toop.app.widget;
import org.toop.app.widget.complex.PopupWidget;
import org.toop.app.widget.complex.PrimaryWidget;
import java.util.ArrayDeque;
import java.util.Deque;
import javafx.application.Platform;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
public final class WidgetContainer { public final class WidgetContainer {
private static final Deque<PopupWidget> popups = new ArrayDeque<>();
private static StackPane root; private static StackPane root;
public static StackPane setup() { public static synchronized StackPane setup() {
if (root != null) { if (root != null) {
return root; return root;
} }
@@ -17,12 +26,45 @@ public final class WidgetContainer {
return root; return root;
} }
public static void add(Pos position, Widget<?> widget) { public static void add(Pos position, Widget widget) {
StackPane.setAlignment(widget.getNode(), position); if (root == null || widget == null) {
root.getChildren().add(widget.getNode()); return;
}
Platform.runLater(() -> {
if (root.getChildren().contains(widget.getNode())) {
return;
}
StackPane.setAlignment(widget.getNode(), position);
if (widget instanceof PrimaryWidget) {
root.getChildren().addFirst(widget.getNode());
} else {
root.getChildren().add(widget.getNode());
}
if (widget instanceof PopupWidget popup) {
popups.push(popup);
}
});
} }
public static void remove(Widget<?> widget) { public static void remove(Widget widget) {
root.getChildren().remove(widget.getNode()); if (root == null || widget == null) {
return;
}
Platform.runLater(() -> {
root.getChildren().remove(widget.getNode());
if (widget instanceof PrimaryWidget) {
for (var popup : popups) {
root.getChildren().remove(popup.getNode());
}
popups.clear();
}
});
} }
} }

View File

@@ -1,26 +1,31 @@
package org.toop.app.widget.complex; package org.toop.app.widget.complex;
import javafx.scene.layout.HBox;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget; import org.toop.app.widget.Widget;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
public class ConfirmWidget implements Widget<VBox> { public class ConfirmWidget implements Widget {
private final HBox buttonsContainer; private final HBox buttonsContainer;
private final VBox container; private final VBox container;
public ConfirmWidget(String confirm) { public ConfirmWidget(String confirm) {
buttonsContainer = Primitive.hbox(); buttonsContainer = Primitive.hbox();
container = Primitive.vbox(Primitive.text(confirm), buttonsContainer); container = Primitive.vbox(Primitive.header(confirm), buttonsContainer);
} }
public void addButton(String label, Runnable onClick) { public void addButton(String key, Runnable onClick) {
var button = Primitive.button(label); Platform.runLater(() -> {
button.setOnAction(_ -> onClick.run()); var button = Primitive.button(key, onClick);
buttonsContainer.getChildren().add(button); buttonsContainer.getChildren().add(button);
} });
}
@Override @Override
public VBox getNode() { return container; } public Node getNode() {
return container;
}
} }

View File

@@ -0,0 +1,42 @@
package org.toop.app.widget.complex;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget;
import java.util.function.Consumer;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
public class LabeledChoiceWidget<T> implements Widget {
private final ComboBox<T> comboBox;
private final VBox container;
@SafeVarargs
public LabeledChoiceWidget(
String key,
StringConverter<T> converter,
T initialValue,
Consumer<T> onValueChanged,
T... items
) {
var label = Primitive.text(key);
comboBox = Primitive.choice(converter, initialValue, onValueChanged, items);
container = Primitive.vbox(label, comboBox);
}
public T getValue() {
return comboBox.getValue();
}
public void setValue(T value) {
comboBox.setValue(value);
}
@Override
public Node getNode() {
return container;
}
}

View File

@@ -0,0 +1,34 @@
package org.toop.app.widget.complex;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget;
import java.util.function.Consumer;
import javafx.scene.Node;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class LabeledInputWidget implements Widget {
private final TextField input;
private final VBox container;
public LabeledInputWidget(String key, String promptKey, String initialText, Consumer<String> onValueChanged) {
var label = Primitive.text(key);
input = Primitive.input(promptKey, initialText, onValueChanged);
container = Primitive.vbox(label, input);
}
public String getValue() {
return input.getText();
}
public void setValue(String text) {
input.setText(text);
}
@Override
public Node getNode() {
return container;
}
}

View File

@@ -0,0 +1,49 @@
package org.toop.app.widget.complex;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget;
import java.util.function.Consumer;
import javafx.scene.Node;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
public class LabeledSliderWidget implements Widget {
private final Slider slider;
private final Text labelValue;
private final VBox container;
public LabeledSliderWidget(String key, int min, int max, int value, Consumer<Integer> onValueChanged) {
var label = Primitive.text(key);
labelValue = new Text(String.valueOf(value));
labelValue.getStyleClass().add("text");
slider = Primitive.slider(min, max, value, newValue -> {
labelValue.setText(String.valueOf(newValue));
if (onValueChanged != null) {
onValueChanged.accept(newValue);
}
});
var sliderRow = Primitive.hbox(slider, labelValue);
container = Primitive.vbox(label, sliderRow);
}
public int getValue() {
return (int)slider.getValue();
}
public void setValue(int newValue) {
slider.setValue(newValue);
labelValue.setText(String.valueOf(newValue));
}
@Override
public Node getNode() {
return container;
}
}

View File

@@ -1,20 +1,7 @@
package org.toop.app.widget.complex; package org.toop.app.widget.complex;
import org.toop.app.interfaces.Popup; public abstract class PopupWidget extends StackWidget {
import org.toop.app.widget.WidgetContainer;
import javafx.geometry.Pos;
public abstract class PopupWidget extends ViewWidget implements Popup {
public PopupWidget() { public PopupWidget() {
super("bg-popup"); super("bg-popup");
} }
public void push() {
WidgetContainer.add(Pos.CENTER, this);
}
public void pop() {
WidgetContainer.remove(this);
}
} }

View File

@@ -1,16 +1,43 @@
package org.toop.app.widget.complex; package org.toop.app.widget.complex;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.Primitive;
public abstract class PrimaryWidget extends StackWidget {
private PrimaryWidget previous = null;
public abstract class PrimaryWidget extends ViewWidget implements TransitionAnimation {
public PrimaryWidget() { public PrimaryWidget() {
super("bg-primary"); super("bg-primary");
} }
@Override public void transitionNext(PrimaryWidget primary) {
public void transition(PrimaryWidget primary) { primary.previous = this;
WidgetContainer.add(Pos.CENTER, primary); replace(Pos.CENTER, primary);
WidgetContainer.remove(this);
} var backButton = Primitive.button("back", () -> {
primary.transitionPrevious();
});
primary.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
}
public void transitionPrevious() {
if (previous == null) {
return;
}
replace(Pos.CENTER, previous);
previous = null;
}
public void reload(PrimaryWidget primary) {
primary.previous = previous;
replace(Pos.CENTER, primary);
var backButton = Primitive.button("back", () -> {
primary.transitionPrevious();
});
primary.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
}
} }

View File

@@ -0,0 +1,47 @@
package org.toop.app.widget.complex;
import org.toop.app.widget.Widget;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
public abstract class StackWidget implements Widget {
private final StackPane container;
public StackWidget(String cssClass) {
container = new StackPane();
container.getStyleClass().add(cssClass);
}
public void add(Pos position, Node node) {
Platform.runLater(() -> {
if (container.getChildren().contains(node)) {
return;
}
StackPane.setAlignment(node, position);
container.getChildren().add(node);
});
}
public void add(Pos position, Widget widget) {
add(position, widget.getNode());
}
public void remove(Node node) {
Platform.runLater(() -> {
container.getChildren().remove(node);
});
}
public void remove(Widget widget) {
remove(widget.getNode());
}
@Override
public Node getNode() {
return container;
}
}

View File

@@ -0,0 +1,62 @@
package org.toop.app.widget.complex;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.Widget;
import org.toop.local.AppContext;
import java.util.function.Consumer;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
public class ToggleWidget implements Widget {
private final Button button;
private final VBox container;
private final String onKey;
private final String offKey;
private boolean state;
public ToggleWidget(String onKey, String offKey, boolean initialState, Consumer<Boolean> onToggle) {
this.onKey = onKey;
this.offKey = offKey;
this.state = initialState;
button = new Button(AppContext.getString(getCurrentKey()));
button.setOnAction(_ -> {
state = !state;
updateText();
if (onToggle != null) {
onToggle.accept(state);
}
});
container = Primitive.vbox(button);
}
private String getCurrentKey() {
return state? offKey : onKey;
}
private void updateText() {
button.setText(AppContext.getString(getCurrentKey()));
}
public boolean getState() {
return state;
}
public void setState(boolean newState) {
if (state != newState) {
state = newState;
updateText();
}
}
@Override
public Node getNode() {
return container;
}
}

View File

@@ -1,5 +0,0 @@
package org.toop.app.widget.complex;
public interface TransitionAnimation {
void transition(PrimaryWidget primary);
}

View File

@@ -1,27 +0,0 @@
package org.toop.app.widget.complex;
import javafx.geometry.Pos;
import org.toop.app.widget.Widget;
import javafx.scene.layout.StackPane;
public abstract class ViewWidget implements Widget<StackPane> {
private final StackPane container;
public ViewWidget(String cssClass) {
container = new StackPane();
container.getStyleClass().add(cssClass);
}
public void add(Pos position, Widget<?> widget) {
StackPane.setAlignment(widget.getNode(), position);
container.getChildren().add(widget.getNode());
}
@Override
public StackPane getNode() {
return container;
}
public abstract void reload();
}

View File

@@ -0,0 +1,117 @@
package org.toop.app.widget.display;
import org.toop.app.widget.Widget;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
public class SongDisplay extends VBox implements Widget {
private final Text songTitle;
private final ProgressBar progressBar;
private final Text progressText;
public SongDisplay() {
new EventFlow()
.listen(this::updateTheSong);
setAlignment(Pos.CENTER);
setMaxHeight(Region.USE_PREF_SIZE);
getStyleClass().add("song-display");
// TODO ADD GOOD SONG TITLES WITH ARTISTS DISPLAYED
songTitle = new Text("song playing");
songTitle.getStyleClass().add("song-title");
progressBar = new ProgressBar(0);
progressBar.getStyleClass().add("progress-bar");
progressText = new Text("0:00/0:00");
progressText.getStyleClass().add("progress-text");
// TODO ADD BETTER CSS FOR THE SKIPBUTTON WHERE ITS AT A NICER POSITION
Button skipButton = new Button(">>");
Button pauseButton = new Button("");
Button previousButton = new Button("<<");
skipButton.getStyleClass().setAll("skip-button");
pauseButton.getStyleClass().setAll("pause-button");
previousButton.getStyleClass().setAll("previous-button");
skipButton.setOnAction( event -> {
GlobalEventBus.post(new AudioEvents.SkipMusic());
});
pauseButton.setOnAction(event -> {
GlobalEventBus.post(new AudioEvents.PauseMusic());
if (pauseButton.getText().equals("")) {
pauseButton.setText("");
}
else if (pauseButton.getText().equals("")) {
pauseButton.setText("");
}
});
previousButton.setOnAction( event -> {
GlobalEventBus.post(new AudioEvents.PreviousMusic());
});
HBox control = new HBox(10, previousButton, pauseButton, skipButton);
control.setAlignment(Pos.CENTER);
control.getStyleClass().add("controls");
getChildren().addAll(songTitle, progressBar, progressText, control);
}
private void updateTheSong(AudioEvents.PlayingMusic event) {
Platform.runLater(() -> {
String text = event.name();
text = text.substring(0, text.length() - 4);
songTitle.setText(text);
double currentPos = event.currentPosition();
double duration = event.duration();
if (currentPos / duration > 0.05) {
double progress = currentPos / duration;
progressBar.setProgress(progress);
}
else if (currentPos / duration < 0.05) {
progressBar.setProgress(0.05);
}
progressText.setText(getTimeString(event.currentPosition(), event.duration()));
});
}
private String getTimeString(long position, long duration) {
long positionMinutes = position / 60;
long durationMinutes = duration / 60;
long positionSeconds = position % 60;
long durationSeconds = duration % 60;
String positionSecondsStr = String.valueOf(positionSeconds);
String durationSecondsStr = String.valueOf(durationSeconds);
if (positionSeconds < 10) {
positionSecondsStr = "0" + positionSeconds;
}
if (durationSeconds < 10) {
durationSecondsStr = "0" + durationSeconds;
}
String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr;
return time;
}
@Override
public Node getNode() {
return this;
}
}

View File

@@ -0,0 +1,24 @@
package org.toop.app.widget.popup;
import org.toop.app.App;
import org.toop.app.widget.complex.ConfirmWidget;
import org.toop.app.widget.complex.PopupWidget;
import javafx.geometry.Pos;
public class QuitPopup extends PopupWidget {
public QuitPopup() {
var confirmWidget = new ConfirmWidget("are-you-sure");
confirmWidget.addButton("yes", () -> {
App.quit();
});
confirmWidget.addButton("no", () -> {
App.stopQuit();
hide();
});
add(Pos.CENTER, confirmWidget);
}
}

View File

@@ -0,0 +1,81 @@
package org.toop.app.widget.primary;
import org.toop.app.App;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.PrimaryWidget;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.geometry.Pos;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;
import javafx.util.Duration;
public class CreditsPrimary extends PrimaryWidget {
public CreditsPrimary() {
var scrumMasterCredit = newCredit("scrum-master", "Stef");
var productOwnerCredit = newCredit("product-owner", "Omar");
var mergeCommanderCredit = newCredit("merge-commander", "Bas");
var localizationCredit = newCredit("localization", "Ticho");
var aiCredit = newCredit("ai", "Michiel");
var developersCredit = newCredit("developers", "Michiel, Bas, Stef, Omar, Ticho");
var moralSupportCredit = newCredit("moral-support", "Wesley");
var openglCredit = newCredit("opengl", "Omar");
var topSpacer = new Region();
topSpacer.setPrefHeight(App.getHeight());
var bottomSpacer = new Region();
bottomSpacer.setPrefHeight(App.getHeight());
var creditsContainer = Primitive.vbox(
topSpacer,
scrumMasterCredit,
productOwnerCredit,
mergeCommanderCredit,
localizationCredit,
aiCredit,
developersCredit,
moralSupportCredit,
openglCredit,
bottomSpacer
);
var creditsScroll = Primitive.scroll(creditsContainer);
creditsScroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
creditsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
add(Pos.CENTER, creditsScroll);
animate(creditsScroll, 15);
}
private HBox newCredit(String key, String other) {
var credit = new Text(": " + other);
credit.getStyleClass().add("header");
var creditBox = Primitive.hbox(
Primitive.header(key),
credit
);
creditBox.setPrefHeight(App.getHeight() / 3.0);
return creditBox;
}
private void animate(ScrollPane scroll, int length) {
final Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0), new KeyValue(scroll.vvalueProperty(), 0.0)),
new KeyFrame(Duration.seconds(length), new KeyValue(scroll.vvalueProperty(), 1.0))
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}

View File

@@ -0,0 +1,25 @@
package org.toop.app.widget.primary;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.PrimaryWidget;
import javafx.geometry.Pos;
public class LocalPrimary extends PrimaryWidget {
public LocalPrimary() {
var ticTacToeButton = Primitive.button("tic-tac-toe", () -> {
});
var reversiButton = Primitive.button("reversi", () -> {
});
var connect4Button = Primitive.button("connect4", () -> {
});
add(Pos.CENTER, Primitive.vbox(
ticTacToeButton,
reversiButton,
connect4Button
));
}
}

View File

@@ -0,0 +1,39 @@
package org.toop.app.widget.primary;
import org.toop.app.App;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.PrimaryWidget;
import javafx.geometry.Pos;
public class MainPrimary extends PrimaryWidget {
public MainPrimary() {
var localButton = Primitive.button("local", () -> {
transitionNext(new LocalPrimary());
});
var onlineButton = Primitive.button("online", () -> {
transitionNext(new OnlinePrimary());
});
var creditsButton = Primitive.button("credits", () -> {
transitionNext(new CreditsPrimary());
});
var optionsButton = Primitive.button("options", () -> {
transitionNext(new OptionsPrimary());
});
var quitButton = Primitive.button("quit", () -> {
App.startQuit();
});
add(Pos.CENTER, Primitive.vbox(
localButton,
onlineButton,
creditsButton,
optionsButton,
quitButton
));
}
}

View File

@@ -0,0 +1,38 @@
package org.toop.app.widget.primary;
import org.toop.app.Server;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.LabeledInputWidget;
import org.toop.app.widget.complex.PrimaryWidget;
import javafx.geometry.Pos;
public class OnlinePrimary extends PrimaryWidget {
public OnlinePrimary() {
var serverInformationHeader = Primitive.header("server-information");
var serverIPInput = new LabeledInputWidget("ip-address", "enter-the-server-ip", "", _ -> {});
var serverPortInput = new LabeledInputWidget("port", "enter-the-server-port", "", _ -> {});
var playerNameInput = new LabeledInputWidget("player-name", "enter-your-name", "", _ -> {});
var connectButton = Primitive.button("connect", () -> {
new Server(
serverIPInput.getValue(),
serverPortInput.getValue(),
playerNameInput.getValue()
);
});
add(Pos.CENTER, Primitive.vbox(
serverInformationHeader,
Primitive.separator(),
serverIPInput.getNode(),
serverPortInput.getNode(),
playerNameInput.getNode(),
Primitive.separator(),
connectButton
));
}
}

View File

@@ -0,0 +1,161 @@
package org.toop.app.widget.primary;
import org.toop.app.App;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.LabeledChoiceWidget;
import org.toop.app.widget.complex.LabeledSliderWidget;
import org.toop.app.widget.complex.PrimaryWidget;
import org.toop.app.widget.complex.ToggleWidget;
import org.toop.framework.audio.VolumeControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext;
import org.toop.local.AppSettings;
import java.util.Locale;
import javafx.geometry.Pos;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
public class OptionsPrimary extends PrimaryWidget {
public OptionsPrimary() {
add(Pos.CENTER, Primitive.hbox(
generalSection(),
volumeSection(),
styleSection()
));
}
private VBox generalSection() {
var languageWidget = new LabeledChoiceWidget<>(
"language",
new StringConverter<>() {
@Override
public String toString(Locale locale) {
return AppContext.getString(locale.getDisplayName().toLowerCase());
}
@Override
public Locale fromString(String s) { return null; }
},
AppContext.getLocale(),
newLocale -> {
AppSettings.getSettings().setLocale(newLocale.toString());
AppContext.setLocale(newLocale);
reload(new OptionsPrimary());
},
AppContext.getLocalization().getAvailableLocales().toArray(new Locale[0])
);
var fullscreenToggle = new ToggleWidget(
"fullscreen", "windowed",
AppSettings.getSettings().getFullscreen(),
fullscreen -> {
AppSettings.getSettings().setFullscreen(fullscreen);
App.setFullscreen(fullscreen);
}
);
return Primitive.vbox(
Primitive.header("general"),
Primitive.separator(),
languageWidget.getNode(),
fullscreenToggle.getNode()
);
}
private VBox volumeSection() {
var masterVolumeWidget = new LabeledSliderWidget(
"master-volume",
0, 100,
AppSettings.getSettings().getVolume(),
val -> {
AppSettings.getSettings().setVolume(val);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.MASTERVOLUME))
.asyncPostEvent();
}
);
var effectsVolumeWidget = new LabeledSliderWidget(
"effects-volume",
0, 100,
AppSettings.getSettings().getFxVolume(),
val -> {
AppSettings.getSettings().setFxVolume(val);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.FX))
.asyncPostEvent();
}
);
var musicVolumeWidget = new LabeledSliderWidget(
"music-volume",
0, 100,
AppSettings.getSettings().getMusicVolume(),
val -> {
AppSettings.getSettings().setMusicVolume(val);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.MUSIC))
.asyncPostEvent();
}
);
return Primitive.vbox(
Primitive.header("volume"),
Primitive.separator(),
masterVolumeWidget.getNode(),
effectsVolumeWidget.getNode(),
musicVolumeWidget.getNode()
);
}
private VBox styleSection() {
var themeWidget = new LabeledChoiceWidget<>(
"theme",
new StringConverter<>() {
@Override
public String toString(String theme) {
return AppContext.getString(theme);
}
@Override
public String fromString(String s) { return null; }
},
AppSettings.getSettings().getTheme(),
newTheme -> {
AppSettings.getSettings().setTheme(newTheme);
App.setStyle(newTheme, AppSettings.getSettings().getLayoutSize());
},
"dark", "light", "high-contrast"
);
var layoutWidget = new LabeledChoiceWidget<>(
"layout-size",
new StringConverter<>() {
@Override
public String toString(String layout) {
return AppContext.getString(layout);
}
@Override
public String fromString(String s) { return null; }
},
AppSettings.getSettings().getLayoutSize(),
newLayout -> {
AppSettings.getSettings().setLayoutSize(newLayout);
App.setStyle(AppSettings.getSettings().getTheme(), newLayout);
},
"small", "medium", "large"
);
return Primitive.vbox(
Primitive.header("style"),
Primitive.separator(),
themeWidget.getNode(),
layoutWidget.getNode()
);
}
}

View File

@@ -1,26 +0,0 @@
package org.toop.app.widget.simple;
import org.toop.app.widget.Widget;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
public class LabeledButtonWidget extends VBox implements Widget<VBox> {
public LabeledButtonWidget(
String labelText,
String buttonText, Runnable buttonOnAction
) {
var text = new Text(labelText);
var button = new Button(buttonText);
button.setOnAction(_ -> buttonOnAction.run());
super(text, button);
}
@Override
public VBox getNode() {
return this;
}
}

View File

@@ -1,4 +0,0 @@
package org.toop.app.widget.view;
public class MainView {
}

View File

@@ -1,12 +0,0 @@
package org.toop.app.widget.view;
import org.toop.app.widget.complex.PopupWidget;
public class QuitView extends PopupWidget {
protected QuitView() {
}
@Override
public void reload() {
}
}

View File

@@ -1,19 +1,28 @@
package org.toop.local; package org.toop.local;
import java.util.Locale;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
import org.toop.framework.resource.resources.LocalizationAsset; import org.toop.framework.resource.resources.LocalizationAsset;
import java.util.Locale;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class AppContext { public class AppContext {
private static final LocalizationAsset localization = ResourceManager.get("localization"); private static final LocalizationAsset localization = ResourceManager.get("localization");
private static Locale locale = Locale.forLanguageTag("en"); private static Locale locale = Locale.forLanguageTag("en");
private static final ObjectProperty<Locale> localeProperty = new SimpleObjectProperty<>(locale);
public static LocalizationAsset getLocalization() { public static LocalizationAsset getLocalization() {
return localization; return localization;
} }
public static void setLocale(Locale locale) { public static void setLocale(Locale locale) {
AppContext.locale = locale; AppContext.locale = locale;
localeProperty.set(locale);
} }
public static Locale getLocale() { public static Locale getLocale() {
@@ -21,7 +30,13 @@ public class AppContext {
} }
public static String getString(String key) { public static String getString(String key) {
assert localization != null;
return localization.getString(key, locale); return localization.getString(key, locale);
} }
public static StringBinding bindToKey(String key) {
return Bindings.createStringBinding(
() -> localization.getString(key, locale),
localeProperty
);
}
} }

View File

@@ -3,36 +3,35 @@
-fx-padding: 0; -fx-padding: 0;
} }
.container, .container {
.credits-container {
-fx-alignment: TOP_CENTER; -fx-alignment: TOP_CENTER;
-fx-background-color: transparent; -fx-background-color: transparent;
} }
.fit, .scroll,
.fit .viewport { .scroll .viewport {
-fx-background-color: transparent; -fx-background-color: transparent;
-fx-border-color: transparent; -fx-border-color: transparent;
} }
.fit .scroll-bar .decrement-arrow, .scroll .scroll-bar .decrement-arrow,
.fit .scroll-bar .decrement-button, .scroll .scroll-bar .decrement-button,
.fit .scroll-bar .increment-arrow, .scroll .scroll-bar .increment-arrow,
.fit .scroll-bar .increment-button { .scroll .scroll-bar .increment-button {
-fx-padding: 0; -fx-padding: 0;
-fx-pref-height: 0; -fx-pref-height: 0;
-fx-pref-width: 0; -fx-pref-width: 0;
-fx-shape: ""; -fx-shape: "";
} }
.fit .scroll-bar .thumb { .scroll .scroll-bar .thumb {
-fx-background-color: #888; -fx-background-color: #888;
-fx-background-insets: 0; -fx-background-insets: 0;
-fx-background-radius: 1px; -fx-background-radius: 1px;
} }
.fit .scroll-bar:horizontal, .scroll .scroll-bar:horizontal,
.fit .scroll-bar:vertical { .scroll .scroll-bar:vertical {
-fx-pref-height: 4px; -fx-pref-height: 4px;
-fx-pref-width: 4px; -fx-pref-width: 4px;
} }