Merge branch 'Development' into OtherGames

This commit is contained in:
Ticho Hidding
2025-10-27 13:53:06 +01:00
26 changed files with 401 additions and 10 deletions

View File

@@ -0,0 +1,90 @@
package org.toop.app.view.displays;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.toop.framework.audio.AudioEventListener;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
import org.toop.framework.eventbus.GlobalEventBus;
public class SongDisplay extends VBox {
private final Text songTitle;
private final ProgressBar progressBar;
private final Text progressText;
public SongDisplay() {
new EventFlow()
.listen(this::updateTheSong);
setAlignment(Pos.CENTER);
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(">>");
skipButton.getStyleClass().setAll("skip-button");
skipButton.setOnAction( event -> {
GlobalEventBus.post(new AudioEvents.SkipMusic());
});
getChildren().addAll(songTitle, progressBar, progressText, skipButton);
}
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;
}
}

View File

@@ -4,6 +4,7 @@ import org.toop.app.GameInformation;
import org.toop.app.Server;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -95,6 +96,14 @@ public final class ChallengeView extends View {
nodes.add(vbox(computerDifficultyText, computerDifficultySlider));
}
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(hboxFill(
vboxFill(

View File

@@ -3,6 +3,7 @@ package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.animation.KeyFrame;
@@ -46,6 +47,14 @@ public final class CreditsView extends View {
final Text openglHeader = header();
openglHeader.setText(AppContext.getString("opengl") + ": Omar");
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit("credits-fit", vboxFill("credits-container", "credits-container",
vbox("credits-spacer-top", ""),

View File

@@ -2,6 +2,7 @@ package org.toop.app.view.views;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -85,6 +86,14 @@ public final class GameView extends View {
forfeitButton = null;
}
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
if (onMessage != null) {
chatListView = new ListView<Text>();

View File

@@ -6,6 +6,7 @@ import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGame;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -51,6 +52,14 @@ public final class LocalMultiplayerView extends View {
}
});
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
hbox(

View File

@@ -3,6 +3,7 @@ package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -39,6 +40,14 @@ public final class LocalView extends View {
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton

View File

@@ -4,7 +4,7 @@ import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.local.AppContext;
import org.toop.app.view.displays.SongDisplay;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@@ -35,6 +35,13 @@ public final class MainView extends View {
quitButton.setText(AppContext.getString("quit"));
quitButton.setOnAction(_ -> { App.startQuit(); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
localButton,

View File

@@ -3,6 +3,7 @@ package org.toop.app.view.views;
import org.toop.app.Server;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -44,6 +45,13 @@ public class OnlineView extends View {
new Server(serverIPInput.getText(), serverPortInput.getText(), playerNameInput.getText());
});
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
serverInformationHeader,

View File

@@ -3,6 +3,7 @@ package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.framework.audio.VolumeControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
@@ -99,6 +100,14 @@ public final class OptionsView extends View {
backButton.setText(AppContext.getString("back"));
backButton.setOnAction(_ -> { ViewStack.pop(); });
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.BOTTOM_LEFT,
vboxFill(
backButton

View File

@@ -2,6 +2,7 @@ package org.toop.app.view.views;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.app.view.displays.SongDisplay;
import org.toop.local.AppContext;
import javafx.application.Platform;
@@ -53,6 +54,14 @@ public final class ServerView extends View {
listView = new ListView<Button>();
final SongDisplay songdisplay = new SongDisplay();
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
songdisplay
)));
add(Pos.CENTER,
fit(vboxFill(
vbox(

View File

@@ -12,6 +12,49 @@
-fx-effect: dropshadow(gaussian, #224455cc, 8, 0, 0, 2);
}
.song-display {
-fx-padding: 8 12 8 12;
-fx-spacing: 6px;
-fx-alignment: center;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 6, 0.5, 0, 2);
-fx-min-width: 220px;
-fx-max-width: 260px;
}
.song-title {
-fx-font-size: 14px;
-fx-fill: white;
}
.progress-bar {
-fx-pref-width: 200px;
-fx-accent: red;
}
.progress-bar > .track {
-fx-background-radius: 30;
}
.progress-bar > .bar {
-fx-background-radius: 30px;
}
.progress-text {
-fx-font-size: 11px;
-fx-fill: white;
}
.skip-button {
-fx-background-color: transparent;
-fx-background-radius: 0;
-fx-cursor: hand;
-fx-text-fill: white;
}
.skip-button .text {
-fx-fill: white;
}
.button {
-fx-background-color: linear-gradient(to bottom, #1f5e20, #2e7d2e);
-fx-background-radius: 8;

View File

@@ -12,6 +12,49 @@
-fx-effect: dropshadow(gaussian, #1f6a22ff, 10, 0, 0, 3);
}
.song-display {
-fx-padding: 8 12 8 12;
-fx-spacing: 6px;
-fx-alignment: center;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 6, 0.5, 0, 2);
-fx-min-width: 220px;
-fx-max-width: 260px;
}
.song-title {
-fx-font-size: 14px;
-fx-fill: white;
}
.progress-bar {
-fx-pref-width: 200px;
-fx-accent: red;
}
.progress-bar > .track {
-fx-background-radius: 30;
}
.progress-bar > .bar {
-fx-background-radius: 30px;
}
.progress-text {
-fx-font-size: 11px;
-fx-fill: white;
}
.skip-button {
-fx-background-color: transparent;
-fx-background-radius: 0;
-fx-cursor: hand;
-fx-text-fill: white;
}
.skip-button .text {
-fx-fill: white;
}
.button {
-fx-background-color: linear-gradient(to bottom, #1b7a1b, #2da32d);
-fx-background-radius: 8;

View File

@@ -12,6 +12,50 @@
-fx-effect: dropshadow(gaussian, #a0c4b088, 6, 0, 0, 2);
}
.song-display {
-fx-padding: 8 12 8 12;
-fx-spacing: 6px;
-fx-alignment: center;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 6, 0.5, 0, 2);
-fx-min-width: 220px;
-fx-max-width: 260px;
}
.song-title {
-fx-font-size: 14px;
-fx-fill: black;
}
.progress-bar {
-fx-inner-background-color: black;
-fx-pref-width: 200px;
-fx-accent: red;
}
.progress-bar > .track {
-fx-background-radius: 30;
}
.progress-bar > .bar {
-fx-background-radius: 30px;
}
.progress-text {
-fx-font-size: 11px;
-fx-fill: black;
}
.skip-button {
-fx-background-color: transparent;
-fx-background-radius: 0;
-fx-cursor: hand;
-fx-text-fill: black;
}
.skip-button .text {
-fx-fill: black;
}
.button {
-fx-background-color: linear-gradient(to bottom, #7ac27a, #90d090);
-fx-background-radius: 8;

View File

@@ -5,6 +5,7 @@ import org.toop.framework.audio.interfaces.MusicManager;
import org.toop.framework.audio.interfaces.SoundEffectManager;
import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.resource.types.AudioResource;
public class AudioEventListener<T extends AudioResource, K extends AudioResource> {
@@ -26,6 +27,7 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
new EventFlow()
.listen(this::handleStopMusicManager)
.listen(this::handlePlaySound)
.listen(this::handleSkipSong)
.listen(this::handleStopSound)
.listen(this::handleMusicStart)
.listen(this::handleVolumeChange)
@@ -44,6 +46,10 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
this.soundEffectManager.play(event.fileName(), event.loop());
}
private void handleSkipSong(AudioEvents.SkipMusic event) {
this.musicManager.skip();
}
private void handleStopSound(AudioEvents.StopEffect event) {
this.soundEffectManager.stop(event.fileName());
}
@@ -57,12 +63,9 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
}
private void handleGetVolume(AudioEvents.GetVolume event) {
new EventFlow()
.addPostEvent(
new AudioEvents.GetVolumeResponse(
GlobalEventBus.postAsync(new AudioEvents.GetVolumeResponse(
audioVolumeManager.getVolume(event.controlType()),
event.identifier()))
.asyncPostEvent();
event.identifier()));
}
}

View File

@@ -2,12 +2,18 @@ package org.toop.framework.audio;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.dispatch.interfaces.Dispatcher;
import org.toop.framework.dispatch.JavaFXDispatcher;
import org.toop.annotations.TestsOnly;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.resource.types.AudioResource;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MusicManager<T extends AudioResource> implements org.toop.framework.audio.interfaces.MusicManager<T> {
private static final Logger logger = LogManager.getLogger(MusicManager.class);
@@ -17,6 +23,8 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
private final List<T> resources;
private int playingIndex = 0;
private boolean playing = false;
private ScheduledExecutorService scheduler;
public MusicManager(List<T> resources, boolean shuffleMusic) {
this.dispatcher = new JavaFXDispatcher();
@@ -66,6 +74,15 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
playCurrentTrack();
}
public void skip() {
if (backgroundMusic.isEmpty()) return;
stop();
scheduler.shutdownNow();
playingIndex = playingIndex + 1;
playing = true;
playCurrentTrack();
}
// Used in testing
void play(int index) {
if (playing) {
@@ -96,17 +113,29 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
current.play();
setTrackRunnable(current);
});
}
private void setTrackRunnable(T track) {
scheduler = Executors.newSingleThreadScheduledExecutor();
Runnable currentMusicTask = new Runnable() {
@Override
public void run() {
GlobalEventBus.post(new AudioEvents.PlayingMusic(track.getName(), track.currentPosition(), track.duration()));
scheduler.schedule(this, 1, TimeUnit.SECONDS);
}
};
track.setOnEnd(() -> {
scheduler.shutdownNow();
playingIndex++;
playCurrentTrack();
});
track.setOnError(() -> {
scheduler.shutdownNow();
logger.error("Error playing track: {}", track);
backgroundMusic.remove(track);
@@ -116,6 +145,8 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
playing = false;
}
});
scheduler.schedule(currentMusicTask, 1, TimeUnit.MILLISECONDS);
}
@Override

View File

@@ -18,6 +18,12 @@ public class AudioEvents extends EventsBase {
/** Start background music. */
public record StartBackgroundMusic() implements GenericEvent {}
/** Gives back the name of the song, the position its currently at (in seconds) and how long it takes (in seconds) */
public record PlayingMusic(String name, long currentPosition, long duration) implements GenericEvent {}
/** Skips the song to the last second of the song resulting in a skip effect */
public record SkipMusic() implements GenericEvent {}
/** Change volume, choose type with {@link VolumeControl}. */
public record ChangeVolume(double newVolume, VolumeControl controlType) implements GenericEvent {}

View File

@@ -5,4 +5,5 @@ import org.toop.framework.resource.types.AudioResource;
public interface MusicManager<T extends AudioResource> extends AudioManager<T> {
void play();
void stop();
void skip();
}

View File

@@ -82,4 +82,21 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR
public void stop() {
getMediaPlayer().stop();
}
@Override
public long duration() {
if (mediaPlayer != null) {
return (long) this.mediaPlayer.getTotalDuration().toSeconds(); // Why is this a double? TODO: Fix cast
}
return 0;
}
@Override
public long currentPosition() {
if (mediaPlayer != null) {
return (long) this.mediaPlayer.getCurrentTime().toSeconds(); // Same here. TODO: Fix cast
}
return 0;
}
}

View File

@@ -40,8 +40,6 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource,
return AudioSystem.getAudioInputStream(decodedFormat, audioInputStream);
}
@Override
public void load() {
try {
@@ -144,4 +142,19 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource,
if (this.clip.isRunning()) this.clip.stop();
}
@Override
public long duration() {
if (clip != null) {
return (long) (clip.getMicrosecondLength() / 1_000_000.0);
}
return 0;
}
@Override
public long currentPosition() {
if (clip != null) {
return (long) (clip.getMicrosecondPosition() / 1_000_000.0);
}
return 0;
}
}

View File

@@ -5,6 +5,8 @@ public interface AudioResource {
void updateVolume(double volume);
void play();
void stop();
long duration();
long currentPosition();
void setOnEnd(Runnable run);
void setOnError(Runnable run);
}

View File

@@ -49,6 +49,16 @@ class MockAudioResource extends BaseResource implements AudioResource {
stopped = true;
}
@Override
public long duration() {
return 0;
}
@Override
public long currentPosition() {
return 0;
}
@Override
public void setOnEnd(Runnable callback) {
onEnd = callback;

View File

@@ -39,6 +39,16 @@ class MockSoundEffectResource extends BaseResource implements AudioResource {
stopped = true;
}
@Override
public long duration() {
return 0;
}
@Override
public long currentPosition() {
return 0;
}
@Override
public void setOnEnd(Runnable callback) {}