Merge branch 'master' into addPlanet

Conflicts:
	src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java
	src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java
This commit is contained in:
OLEGSHA 2021-07-12 16:20:15 +03:00
commit 0264e512ab
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
55 changed files with 2346 additions and 228 deletions

View File

@ -16,7 +16,7 @@ temperature mechanics and a parallelism-capable server.
- GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64) - GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
- Java 8 or later - Java 8 or later
- OpenGL 2.1 or later - OpenGL 2.1 or later
- Probably at least 4 GiB RAM - Probably about 0.5 GiB RAM
- Less than 1 GiB of storage space - Less than 1 GiB of storage space
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements. See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.

69
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,69 @@
# Contributing Guidelines
This document lists conventions adopted by Progressia developers.
## git
### Branches
Progressia repository contains a `master` branch and several "feature" branches.
`master` is expected to contain a version of the game suitable for demonstration and forking/branching. Do not commit directly to `master` without OLEGSHA's approval.
- `master` must always correctly build without compiler warnings (see below).
- `master` must always pass all unit tests.
- `master` must always be able to launch successfully.
- `master` must always only contain working features.
- `master` should not contain excessive debug code.
- `master` must always have its code and filenames formatted (see below).
"Feature" branches are branches dedicated to the development of a single feature. When the feature reaches completion the branch is merged into `master` and removed. Intermediate merges into `master` may occur when some fitting milestone is reached. Intermediate merges from `master` may be done as necessary. Merges between "feature" branches should generally be avoided.
When beginning work on a new feature, create a new branch based on `master` (or on another "feature" branch if absolutely necessary). Use `all-small-with-dashes` to name the branch: `add-trees` or `rebalance-plastics` are good names. Do not fix unrelated bugs or work on unrelated features in a "feature" branch - create a new one, switch to an existing one or commit directly to `master` if the changes are small enough.
"Feature" branches may not be formatted properly. Formatting is required before merging into `master` or other branches.
### Commits
- Commits must leave the branch in a state that builds without compiler warnings (see below).
- Changes should be grouped in commits semantically. Avoid committing many small related changes in sequence; if necessary, wait and accumulate them. Avoid committing unrelated changes together; if necessary, split staged changes into several commits. This should normally result in about 1-2 commits for a day's work.
- Commit bulk changes (renaming, formatting, ...) separately. Don't ever commit whitespace changes outside formatting commits.
- Message format:
```
Short description of changes
<empty line>
- Enumeration of changes
- Nest when appropriate
- Use dashes only
- List not needed for small commits
```
Example:
```
Changed packages for relations, renamed Face to ShapePart
- Added BlockRelation as an abstract superclass to existing relations
- Must be given an absolute "up" direction before use
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
- Renamed Face to ShapePart to reduce confusion with AbsFace
```
- Only commit changes described in the commit message. Please double-check staged files before committing.
- Avoid merge conflicts. Pull before committing.
- Better sign commits than not. See: [git](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), [GitHub](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification).
## Code
### Warnings
Make sure that all committed code contains no compiler warnings. This specifically includes unused imports, unused private members, missing `@Override`s and warnings related to generics.
Warnings about unknown tokens in `@SuppressWarnings` are temporarily ignored. Please disable them in your IDE.
### Code Style
Formatting code is important.
- The format is specified within the files inside `/templates_and_presets/eclipse_ide`. Import the specifications into Eclipse or IntelliJ IDEA and use the IDEs' format feature. Alternatively format the code manually in accordance with existing files.
- Only use tabs for indentation. Never indent with spaces even when wrapping lines. This is to ensure that indentation does not break when tab width is different.
- Don't use `I` prefix for interfaces (not `IDoable` but `Doable`).
- Prioritize readability over compactness. Do not hesitate to use (very) long identifiers if they aid comprehension.
- Document all mathematics unless it is trivial, especially when using math notation for variable names.
- Use proper English when writing comments. Avoid boxes in comments. Use `//` for single-line comments.

View File

@ -40,6 +40,7 @@ public class ProgressiaLauncher {
CrashReports.registerProvider(new OpenALContextProvider()); CrashReports.registerProvider(new OpenALContextProvider());
CrashReports.registerProvider(new ArgsContextProvider()); CrashReports.registerProvider(new ArgsContextProvider());
CrashReports.registerProvider(new LanguageContextProvider()); CrashReports.registerProvider(new LanguageContextProvider());
CrashReports.registerProvider(new ScreenContextProvider());
// Analyzers // Analyzers
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer()); CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());

View File

@ -32,6 +32,7 @@ import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
public class ClientProxy implements Proxy { public class ClientProxy implements Proxy {
@ -59,6 +60,8 @@ public class ClientProxy implements Proxy {
ServerState.startServer(); ServerState.startServer();
ClientState.connectToLocalServer(); ClientState.connectToLocalServer();
TestMusicPlayer.start();
} }
} }

View File

@ -23,6 +23,7 @@ import ru.windcorp.progressia.client.audio.backend.AudioReader;
import ru.windcorp.progressia.client.audio.backend.Listener; import ru.windcorp.progressia.client.audio.backend.Listener;
import ru.windcorp.progressia.client.audio.backend.SoundType; import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.resource.Resource;
import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.AL11.*;
import static org.lwjgl.openal.ALC10.*; import static org.lwjgl.openal.ALC10.*;
@ -40,7 +41,6 @@ public class AudioManager {
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM); private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
private static Speaker musicSpeaker; private static Speaker musicSpeaker;
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
public static void initAL() { public static void initAL() {
String defaultDeviceName = alcGetString( String defaultDeviceName = alcGetString(
@ -82,31 +82,19 @@ public class AudioManager {
return speaker; return speaker;
} }
private static SoundType findSoundType(String soundID) throws Exception { public static Speaker initSpeaker(SoundType st) {
for (SoundType s : soundsBuffer) {
if (s.getId().equals(soundID)) {
return s;
}
}
throw new Exception(
"ERROR: The selected sound is not loaded or" +
" not exists"
);
}
public static Speaker initSpeaker(String soundID) {
Speaker speaker = getLastSpeaker(); Speaker speaker = getLastSpeaker();
try { try {
findSoundType(soundID).initSpeaker(speaker); st.initSpeaker(speaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); throw new RuntimeException();
} }
return speaker; return speaker;
} }
public static Speaker initMusicSpeaker(String soundID) { public static Speaker initMusicSpeaker(SoundType st) {
try { try {
findSoundType(soundID).initSpeaker(musicSpeaker); st.initSpeaker(musicSpeaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); throw new RuntimeException();
} }
@ -120,11 +108,11 @@ public class AudioManager {
} }
} }
public static void loadSound(String path, String id, AudioFormat format) { public static void loadSound(Resource resource, String id, AudioFormat format) {
if (format == AudioFormat.MONO) { if (format == AudioFormat.MONO) {
soundsBuffer.add(AudioReader.readAsMono(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
} else { } else {
soundsBuffer.add(AudioReader.readAsStereo(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsStereo(resource, id));
} }
} }

View File

@ -0,0 +1,34 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.audio;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
public class AudioRegistry extends NamespacedInstanceRegistry<SoundType> {
private static final AudioRegistry INSTANCE = new AudioRegistry();
/**
* @return the instance
*/
public static AudioRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -18,6 +18,8 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class AudioSystem { public class AudioSystem {
static public void initialize() { static public void initialize() {
AudioManager.initAL(); AudioManager.initAL();
@ -28,7 +30,7 @@ public class AudioSystem {
static void loadAudioData() { static void loadAudioData() {
AudioManager.loadSound( AudioManager.loadSound(
"assets/sounds/block_destroy_clap.ogg", ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"Progressia:BlockDestroy", "Progressia:BlockDestroy",
AudioFormat.MONO AudioFormat.MONO
); );

View File

@ -19,72 +19,37 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class Music extends Namespaced { public class Music
private Vec3 position = new Vec3(); extends Sound {
private Vec3 velocity = new Vec3();
private float pitch = 1.0f;
private float gain = 1.0f;
public Music(SoundType soundType, int timeLength, float pitch, float gain) {
super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);
}
public Music(SoundType soundType) {
super(soundType);
}
public Music(String id, int timeLength, float pitch, float gain) {
super(id, timeLength, new Vec3(), new Vec3(), pitch, gain);
}
public Music(String id) { public Music(String id) {
super(id); super(id);
} }
public Music( @Override
String id, protected Speaker initSpeaker() {
Vec3 position, return AudioManager.initMusicSpeaker(soundType);
Vec3 velocity,
float pitch,
float gain
) {
this(id);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
} }
public void play(boolean loop) { @Override
Speaker speaker = AudioManager.initMusicSpeaker(this.getId()); public void setPosition(Vec3 position) {
speaker.setGain(gain); throw new UnsupportedOperationException();
speaker.setPitch(pitch);
speaker.setPosition(position);
speaker.setVelocity(velocity);
if (loop) {
speaker.playLoop();
} else {
speaker.play();
}
}
public void setGain(float gain) {
this.gain = gain;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public void setVelocity(Vec3 velocity) {
this.velocity = velocity;
}
public Vec3 getPosition() {
return position;
}
public float getGain() {
return gain;
}
public Vec3 getVelocity() {
return velocity;
}
public float getPitch() {
return pitch;
} }
} }

View File

@ -19,23 +19,30 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class SoundEffect public class Sound {
extends Namespaced {
private Vec3 position = new Vec3(); protected Vec3 position = new Vec3(0f, 0f, 0f);
private Vec3 velocity = new Vec3(); protected Vec3 velocity = new Vec3(0f, 0f, 0f);
private float pitch = 1.0f; protected float pitch = 1.0f;
private float gain = 1.0f; protected float gain = 1.0f;
protected int timeLength = 0;
public SoundEffect(String id) {
super(id); protected SoundType soundType;
public Sound(SoundType soundType) {
this.soundType = soundType;
} }
public SoundEffect( public Sound(String id) {
this(AudioRegistry.getInstance().get(id));
}
public Sound(
String id, String id,
int timeLength,
Vec3 position, Vec3 position,
Vec3 velocity, Vec3 velocity,
float pitch, float pitch,
@ -47,9 +54,28 @@ public class SoundEffect
this.pitch = pitch; this.pitch = pitch;
this.gain = gain; this.gain = gain;
} }
public Sound(
SoundType soundType,
int timeLength,
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
this(soundType);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
}
protected Speaker initSpeaker() {
return AudioManager.initSpeaker(soundType);
}
public void play(boolean loop) { public void play(boolean loop) {
Speaker speaker = AudioManager.initSpeaker(this.getId()); Speaker speaker = initSpeaker();
speaker.setGain(gain); speaker.setGain(gain);
speaker.setPitch(pitch); speaker.setPitch(pitch);
speaker.setPosition(position); speaker.setPosition(position);
@ -93,4 +119,9 @@ public class SoundEffect
public float getPitch() { public float getPitch() {
return pitch; return pitch;
} }
public double getDuration() {
return soundType.getDuration();
}
} }

View File

@ -33,13 +33,11 @@ public class AudioReader {
} }
// TODO fix converting from mono-stereo // TODO fix converting from mono-stereo
private static SoundType readAsSpecified(String path, String id, int format) { private static SoundType readAsSpecified(Resource resource, String id, int format) {
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1); IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1); IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
Resource res = ResourceManager.getResource(path); ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
return new SoundType( return new SoundType(
id, id,
@ -49,12 +47,12 @@ public class AudioReader {
); );
} }
public static SoundType readAsMono(String path, String id) { public static SoundType readAsMono(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_MONO16); return readAsSpecified(resource, id, AL_FORMAT_MONO16);
} }
public static SoundType readAsStereo(String path, String id) { public static SoundType readAsStereo(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_STEREO16); return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
} }
private static ShortBuffer decodeVorbis( private static ShortBuffer decodeVorbis(

View File

@ -21,6 +21,9 @@ package ru.windcorp.progressia.client.audio.backend;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
import org.lwjgl.openal.AL10;
import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.AL11.*;
public class SoundType extends Namespaced { public class SoundType extends Namespaced {
@ -29,6 +32,7 @@ public class SoundType extends Namespaced {
private int sampleRate; private int sampleRate;
private int format; private int format;
private int audioBuffer; private int audioBuffer;
private double duration;
public SoundType( public SoundType(
String id, String id,
@ -46,9 +50,14 @@ public class SoundType extends Namespaced {
private void createAudioBuffer() { private void createAudioBuffer() {
this.audioBuffer = alGenBuffers(); this.audioBuffer = alGenBuffers();
alBufferData(audioBuffer, format, rawAudio, sampleRate); alBufferData(audioBuffer, format, rawAudio, sampleRate);
duration = rawAudio.limit() / (double) sampleRate / (format == AL10.AL_FORMAT_STEREO16 ? 2 : 1);
} }
public void initSpeaker(Speaker speaker) { public void initSpeaker(Speaker speaker) {
speaker.setAudioData(audioBuffer); speaker.setAudioData(audioBuffer);
} }
}
public double getDuration() {
return duration;
}
}

View File

@ -120,6 +120,7 @@ public class Speaker {
} }
public void setAudioData(int audioData) { public void setAudioData(int audioData) {
stop();
this.audioData = audioData; this.audioData = audioData;
alSourcei(this.sourceData, AL_BUFFER, audioData); alSourcei(this.sourceData, AL_BUFFER, audioData);
} }

View File

@ -0,0 +1,53 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.comms.controls;
import java.util.function.Consumer;
import java.util.function.Predicate;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
public class ControlTriggerLocalLambda extends ControlTriggerInputBased {
private final Predicate<InputEvent> predicate;
private final Consumer<InputEvent> action;
public ControlTriggerLocalLambda(
String id,
Predicate<InputEvent> predicate,
Consumer<InputEvent> action
) {
super(id);
this.predicate = predicate;
this.action = action;
}
@Override
public PacketControl onInputEvent(InputEvent event) {
if (!predicate.test(event))
return null;
action.accept(event);
return null;
}
}

View File

@ -142,6 +142,96 @@ public class ControlTriggers {
predicates predicates
); );
} }
//
//
///
///
//
//
//
//
//
//
//
//
//
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,
Predicate<InputEvent> predicate
) {
return new ControlTriggerLocalLambda(id, predicate, action);
}
public static ControlTriggerInputBased localOf(
String id,
Runnable action,
Predicate<InputEvent> predicate
) {
return localOf(
id,
input -> action.run(),
predicate
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Class<I> inputType,
Consumer<I> action,
Predicate<I>... predicates
) {
return localOf(
id,
createCheckedAction(inputType, action),
createCheckedCompoundPredicate(inputType, predicates)
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Class<I> inputType,
Runnable action,
Predicate<I>... predicates
) {
return localOf(
id,
inputType,
input -> action.run(),
predicates
);
}
@SafeVarargs
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,
Predicate<InputEvent>... predicates
) {
return localOf(
id,
InputEvent.class,
action,
predicates
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Runnable action,
Predicate<InputEvent>... predicates
) {
return of(
id,
input -> action.run(),
predicates
);
}
private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter( private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
Class<I> inputType, Class<I> inputType,
@ -149,6 +239,13 @@ public class ControlTriggers {
) { ) {
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control); return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
} }
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(
Class<I> inputType,
Consumer<I> action
) {
return inputEvent -> action.accept(inputType.cast(inputEvent));
}
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate( private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
Class<I> inputType, Class<I> inputType,

View File

@ -34,7 +34,13 @@ public class Colors {
DEBUG_BLUE = toVector(0xFF0000FF), DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF), DEBUG_CYAN = toVector(0xFF00FFFF),
DEBUG_MAGENTA = toVector(0xFFFF00FF), DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00); DEBUG_YELLOW = toVector(0xFFFFFF00),
LIGHT_GRAY = toVector(0xFFCBCBD0),
BLUE = toVector(0xFF37A2E6),
HOVER_BLUE = toVector(0xFFC3E4F7),
DISABLED_GRAY = toVector(0xFFE5E5E5),
DISABLED_BLUE = toVector(0xFFB2D8ED);
public static Vec4 toVector(int argb) { public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4()); return toVector(argb, new Vec4());

View File

@ -24,6 +24,7 @@ import java.util.List;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorEvent; import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent; import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent; import ru.windcorp.progressia.client.graphics.input.InputEvent;
@ -57,15 +58,24 @@ public class GUI {
} }
public static void addBottomLayer(Layer layer) { public static void addBottomLayer(Layer layer) {
modify(layers -> layers.add(layer)); modify(layers -> {
layers.add(layer);
layer.onAdded();
});
} }
public static void addTopLayer(Layer layer) { public static void addTopLayer(Layer layer) {
modify(layers -> layers.add(0, layer)); modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
} }
public static void removeLayer(Layer layer) { public static void removeLayer(Layer layer) {
modify(layers -> layers.remove(layer)); modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
} }
private static void modify(LayerStackModification mod) { private static void modify(LayerStackModification mod) {
@ -78,12 +88,33 @@ public class GUI {
public static void render() { public static void render() {
synchronized (LAYERS) { synchronized (LAYERS) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear(); if (!MODIFICATION_QUEUE.isEmpty()) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
for (Layer layer : LAYERS) {
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
policy = currentPolicy;
break;
}
}
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
}
}
for (int i = LAYERS.size() - 1; i >= 0; --i) { for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render(); LAYERS.get(i).render();
} }
} }
} }

View File

@ -30,15 +30,52 @@ public abstract class Layer {
private boolean hasInitialized = false; private boolean hasInitialized = false;
private final AtomicBoolean isValid = new AtomicBoolean(false); private final AtomicBoolean isValid = new AtomicBoolean(false);
/**
* Represents various requests that a {@link Layer} can make regarding the
* presence of a visible cursor. The value of the highest layer that is not
* {@link #INDIFFERENT} is used.
*/
public static enum CursorPolicy {
/**
* Require that a cursor is visible.
*/
REQUIRE,
/**
* The {@link Layer} should not affect the presence or absence of a
* visible cursor; lower layers should be consulted.
*/
INDIFFERENT,
/**
* Forbid a visible cursor.
*/
FORBID
}
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
public Layer(String name) { public Layer(String name) {
this.name = name; this.name = name;
} }
public String getName() {
return name;
}
@Override @Override
public String toString() { public String toString() {
return "Layer " + name; return "Layer " + name;
} }
public CursorPolicy getCursorPolicy() {
return cursorPolicy;
}
public void setCursorPolicy(CursorPolicy cursorPolicy) {
this.cursorPolicy = cursorPolicy;
}
void render() { void render() {
GraphicsInterface.startNextLayer(); GraphicsInterface.startNextLayer();
@ -78,5 +115,13 @@ public abstract class Layer {
protected int getHeight() { protected int getHeight() {
return GraphicsInterface.getFrameHeight(); return GraphicsInterface.getFrameHeight();
} }
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
} }

View File

@ -18,11 +18,11 @@
package ru.windcorp.progressia.client.graphics.backend; package ru.windcorp.progressia.client.graphics.backend;
import static org.lwjgl.opengl.GL11.*;
import glm.vec._2.i.Vec2i; import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
public class GraphicsBackend { public class GraphicsBackend {
@ -38,9 +38,30 @@ public class GraphicsBackend {
private static boolean faceCullingEnabled = false; private static boolean faceCullingEnabled = false;
private static boolean isFullscreen = false;
private static boolean vSyncEnabled = false;
private static boolean isGLFWInitialized = false;
private static boolean isOpenGLInitialized = false;
private GraphicsBackend() { private GraphicsBackend() {
} }
public static boolean isGLFWInitialized() {
return isGLFWInitialized;
}
static void setGLFWInitialized(boolean isGLFWInitialized) {
GraphicsBackend.isGLFWInitialized = isGLFWInitialized;
}
public static boolean isOpenGLInitialized() {
return isOpenGLInitialized;
}
static void setOpenGLInitialized(boolean isOpenGLInitialized) {
GraphicsBackend.isOpenGLInitialized = isOpenGLInitialized;
}
public static void initialize() { public static void initialize() {
startRenderThread(); startRenderThread();
} }
@ -128,4 +149,61 @@ public class GraphicsBackend {
faceCullingEnabled = useFaceCulling; faceCullingEnabled = useFaceCulling;
} }
public static boolean isFullscreen() {
return isFullscreen;
}
public static boolean isVSyncEnabled() {
return vSyncEnabled;
}
public static void setFullscreen() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(
getWindowHandle(),
glfwGetPrimaryMonitor(),
0,
0,
vidmode.width(),
vidmode.height(),
0);
isFullscreen = true;
}
public static void setWindowed() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(
getWindowHandle(),
0,
(vidmode.width() - getFrameWidth()) / 2,
(vidmode.height() - getFrameHeight()) / 2,
getFrameWidth(),
getFrameHeight(),
0);
isFullscreen = false;
}
public static void setVSyncEnabled(boolean enable) {
glfwSwapInterval(enable ? 1 : 0);
vSyncEnabled = enable;
}
public static int getRefreshRate() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
public static boolean isMouseCaptured() {
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
if (!capture) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
} }

View File

@ -73,4 +73,21 @@ public class GraphicsInterface {
GraphicsBackend.startNextLayer(); GraphicsBackend.startNextLayer();
} }
public static void makeFullscreen(boolean state) {
if (state) {
GraphicsBackend.setFullscreen();
} else {
GraphicsBackend.setWindowed();
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
public static boolean isMouseCaptured() {
return GraphicsBackend.isMouseCaptured();
}
public static void setMouseCaptured(boolean capture) {
GraphicsBackend.setMouseCaptured(capture);
}
} }

View File

@ -50,6 +50,7 @@ class LWJGLInitializer {
private static void initializeGLFW() { private static void initializeGLFW() {
// TODO Do GLFW error handling: check glfwInit, setup error callback // TODO Do GLFW error handling: check glfwInit, setup error callback
glfwInit(); glfwInit();
GraphicsBackend.setGLFWInitialized(true);
} }
private static void createWindow() { private static void createWindow() {
@ -64,10 +65,8 @@ class LWJGLInitializer {
GraphicsBackend.setWindowHandle(handle); GraphicsBackend.setWindowHandle(handle);
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(handle); glfwMakeContextCurrent(handle);
glfwSwapInterval(0); glfwSwapInterval(0); // TODO: remove after config system is added
} }
private static void positionWindow() { private static void positionWindow() {
@ -87,6 +86,7 @@ class LWJGLInitializer {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects); RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
GraphicsBackend.setOpenGLInitialized(true);
} }
private static void setupWindowCallbacks() { private static void setupWindowCallbacks() {

View File

@ -189,13 +189,10 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) { public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces(); assembleCurrentClipFromFaces();
assembled.add(
new Clip( float depth = this.depth--;
maskStack, Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
getTransform(), assembled.add(new Clip(maskStack, transform, renderable));
renderable
)
);
} }
protected void addFaceToCurrentClip(ShapePart face) { protected void addFaceToCurrentClip(ShapePart face) {

View File

@ -0,0 +1,151 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public abstract class BasicButton extends Component {
private final Label label;
private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
public BasicButton(String name, String label, Font labelFont) {
super(name);
this.label = new Label(name + ".Label", labelFont, label);
setLayout(new LayoutAlign(10));
addChild(this.label);
setFocusable(true);
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
// Click triggers
addListener(KeyEvent.class, e -> {
if (e.isRepeat()) {
return false;
} else if (
e.isLeftMouseButton() ||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
) {
setPressed(e.isPress());
return true;
} else {
return false;
}
});
addListener(new Object() {
// Release when losing focus
@Subscribe
public void onFocusChange(FocusEvent e) {
if (!e.getNewState()) {
setPressed(false);
}
}
// Release when hover ends
@Subscribe
public void onHoverEnded(HoverEvent e) {
if (!e.isNowHovered()) {
setPressed(false);
}
}
// Release when disabled
@Subscribe
public void onDisabled(EnableEvent e) {
if (!e.getComponent().isEnabled()) {
setPressed(false);
}
}
// Trigger virtualClick when button is released
@Subscribe
public void onRelease(ButtonEvent.Release e) {
virtualClick();
}
});
}
public BasicButton(String name, String label) {
this(name, label, new Font());
}
public boolean isPressed() {
return isPressed;
}
public void click() {
setPressed(true);
setPressed(false);
}
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
if (isPressed) {
takeFocus();
}
dispatchEvent(ButtonEvent.create(this, this.isPressed));
}
}
public BasicButton addAction(Consumer<BasicButton> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public void virtualClick() {
this.actions.forEach(action -> {
action.accept(this);
});
}
public Label getLabel() {
return label;
}
}

View File

@ -0,0 +1,79 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.Colors;
public class Button extends BasicButton {
public Button(String name, String label, Font labelFont) {
super(name, label, labelFont);
}
public Button(String name, String label) {
this(name, label, new Font());
}
@Override
protected void assembleSelf(RenderTarget target) {
// Border
Vec4 borderColor;
if (isPressed() || isHovered() || isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
// Inside area
if (isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (isHovered() && isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
}
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
public class Checkbox extends BasicButton {
private class Tick extends Component {
public Tick() {
super(Checkbox.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(x, y, size, size, borderColor);
// Inside area
if (Checkbox.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
}
// "Tick"
if (Checkbox.this.isChecked()) {
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
}
}
}
private boolean checked;
public Checkbox(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
group.setLayoutHint(basicChild.getLayoutHint());
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addAction(b -> switchState());
}
public Checkbox(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public Checkbox(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public Checkbox(String name, String label) {
this(name, label, false);
}
public void switchState() {
setChecked(!isChecked());
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -19,18 +19,23 @@
package ru.windcorp.progressia.client.graphics.gui; package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i; import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker; import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget; import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent; import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent; import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent; import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent; import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent; import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
@ -61,6 +66,8 @@ public class Component extends Named {
private Object layoutHint = null; private Object layoutHint = null;
private Layout layout = null; private Layout layout = null;
private boolean isEnabled = true;
private boolean isFocusable = false; private boolean isFocusable = false;
private boolean isFocused = false; private boolean isFocused = false;
@ -285,9 +292,30 @@ public class Component extends Named {
return this; return this;
} }
/**
* Checks whether this component is focusable. A component needs to be
* focusable to become focused. A component that is focusable may not
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
*
* @return {@code true} iff the component is focusable
* @see #canGainFocusNow()
*/
public boolean isFocusable() { public boolean isFocusable() {
return isFocusable; return isFocusable;
} }
/**
* Checks whether this component can become focused at this moment.
* <p>
* The implementation of this method in {@link Component} considers the
* component a focus candidate if it is both focusable and enabled.
*
* @return {@code true} iff the component can receive focus
* @see #isFocusable()
*/
public boolean canGainFocusNow() {
return isFocusable() && isEnabled();
}
public Component setFocusable(boolean focusable) { public Component setFocusable(boolean focusable) {
this.isFocusable = focusable; this.isFocusable = focusable;
@ -337,7 +365,7 @@ public class Component extends Named {
return; return;
} }
if (component.isFocusable()) { if (component.canGainFocusNow()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
@ -379,7 +407,7 @@ public class Component extends Named {
return; return;
} }
if (component.isFocusable()) { if (component.canGainFocusNow()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
@ -432,13 +460,52 @@ public class Component extends Named {
return null; return null;
} }
public boolean isEnabled() {
return isEnabled;
}
/**
* Enables or disables this component. An {@link EnableEvent} is dispatched
* if the state changes.
*
* @param enabled {@code true} to enable the component, {@code false} to
* disable the component
* @see #setEnabledRecursively(boolean)
*/
public void setEnabled(boolean enabled) {
if (this.isEnabled != enabled) {
if (isFocused() && isEnabled()) {
focusNext();
}
if (isEnabled()) {
setHovered(false);
}
this.isEnabled = enabled;
dispatchEvent(new EnableEvent(this));
}
}
/**
* Enables or disables this component and all of its children recursively.
*
* @param enabled {@code true} to enable the components, {@code false} to
* disable the components
* @see #setEnabled(boolean)
*/
public void setEnabledRecursively(boolean enabled) {
setEnabled(enabled);
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
}
public boolean isHovered() { public boolean isHovered() {
return isHovered; return isHovered;
} }
protected void setHovered(boolean isHovered) { protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered) { if (this.isHovered != isHovered && isEnabled()) {
this.isHovered = isHovered; this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) { if (!isHovered && !getChildren().isEmpty()) {
@ -502,7 +569,7 @@ public class Component extends Named {
} }
protected void handleInput(Input input) { protected void handleInput(Input input) {
if (inputBus != null) { if (inputBus != null && isEnabled()) {
inputBus.dispatch(input); inputBus.dispatch(input);
} }
} }
@ -598,6 +665,17 @@ public class Component extends Named {
} }
} }
/**
* Schedules the reassembly to occur.
* <p>
* This method is invoked in root components whenever a
* {@linkplain #requestReassembly() reassembly request} is made by one of
* its children. When creating the dedicated root component, override this
* method to perform any implementation-specific actions that will cause a
* reassembly as soon as possible.
* <p>
* The default implementation of this method does nothing.
*/
protected void handleReassemblyRequest() { protected void handleReassemblyRequest() {
// To be overridden // To be overridden
} }
@ -637,6 +715,135 @@ public class Component extends Named {
protected void assembleChildren(RenderTarget target) { protected void assembleChildren(RenderTarget target) {
getChildren().forEach(child -> child.assemble(target)); getChildren().forEach(child -> child.assemble(target));
} }
/*
* Automatic Reassembly
*/
/**
* The various kinds of changes that may be used with
* {@link Component#reassembleAt(ARTrigger...)}.
*/
protected static enum ARTrigger {
/**
* Reassemble the component whenever its hover status changes, e.g.
* whenever the pointer enters or leaves its bounds.
*/
HOVER,
/**
* Reassemble the component whenever it gains or loses focus.
* <p>
* <em>Component must be focusable to be able to gain focus.</em> The
* component will not be reassembled unless
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
* invoked.
*/
FOCUS,
/**
* Reassemble the component whenever it is enabled or disabled.
*/
ENABLE
}
/**
* All trigger objects (event listeners) that are currently registered with
* {@link #eventBus}. The field is {@code null} until the first trigger is
* installed.
*/
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
private Object createTriggerObject(ARTrigger type) {
switch (type) {
case HOVER:
return new Object() {
@Subscribe
public void onHoverChanged(HoverEvent e) {
requestReassembly();
}
};
case FOCUS:
return new Object() {
@Subscribe
public void onFocusChanged(FocusEvent e) {
requestReassembly();
}
};
case ENABLE:
return new Object() {
@Subscribe
public void onEnabled(EnableEvent e) {
requestReassembly();
}
};
default:
throw new NullPointerException("type");
}
}
/**
* Requests that {@link #requestReassembly()} is invoked on this component
* whenever any of the specified changes occur. Duplicate attempts to
* register the same trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to
* request reassembly with.
* @see #disableAutoReassemblyAt(ARTrigger...)
*/
protected synchronized void reassembleAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null) {
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
}
for (ARTrigger trigger : triggers) {
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
Object triggerObject = createTriggerObject(trigger);
addListener(trigger);
autoReassemblyTriggerObjects.put(trigger, triggerObject);
}
}
}
/**
* Requests that {@link #requestReassembly()} is no longer invoked on this
* component whenever any of the specified changes occur. After a trigger is
* removed, it may be reinstalled with
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
* nonexistant trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to remove
* @see #reassemblyAt(ARTrigger...)
*/
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null)
return;
for (ARTrigger trigger : triggers) {
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
if (triggerObject != null) {
removeListener(trigger);
}
}
}
// /** // /**
// * Returns a component that displays this component in its center. // * Returns a component that displays this component in its center.

View File

@ -0,0 +1,28 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
public class Group extends Component {
public Group(String name, Layout layout) {
super(name);
setLayout(layout);
}
}

View File

@ -82,6 +82,11 @@ public class Label extends Component {
public Font getFont() { public Font getFont() {
return font; return font;
} }
public void setFont(Font font) {
this.font = font;
requestReassembly();
}
public String getCurrentText() { public String getCurrentText() {
return currentText; return currentText;
@ -96,11 +101,7 @@ public class Label extends Component {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x); float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform( target.pushTransform(
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf new Mat4().identity().translate(startX, getY(), 0).scale(2)
// is this
// magic
// <---
.scale(2)
); );
target.addCustomRenderer(font.assemble(currentText, maxWidth)); target.addCustomRenderer(font.assemble(currentText, maxWidth));

View File

@ -15,14 +15,66 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.client.graphics.gui; package ru.windcorp.progressia.client.graphics.gui;
public class Panel extends Component { import java.util.Objects;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
public class Panel extends Group {
private Vec4 fill;
private Vec4 border;
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
super(name, layout);
this.fill = Objects.requireNonNull(fill, "fill");
this.border = border;
}
public Panel(String name, Layout layout) { public Panel(String name, Layout layout) {
super(name); this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
setLayout(layout); }
/**
* @return the fill
*/
public Vec4 getFill() {
return fill;
}
/**
* @param fill the fill to set
*/
public void setFill(Vec4 fill) {
this.fill = Objects.requireNonNull(fill, "fill");
}
/**
* @return the border
*/
public Vec4 getBorder() {
return border;
}
/**
* @param border the border to set
*/
public void setBorder(Vec4 border) {
this.border = border;
}
@Override
protected void assembleSelf(RenderTarget target) {
if (border == null) {
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
} else {
target.fill(getX(), getY(), getWidth(), getHeight(), border);
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
}
} }
} }

View File

@ -0,0 +1,205 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class RadioButton extends BasicButton {
private class Tick extends Component {
public Tick() {
super(RadioButton.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
target.fill(x + 4, y, size - 8, size, color);
target.fill(x + 2, y + 2, size - 4, size - 4, color);
target.fill(x, y + 4, size, size - 8, color);
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
cross(target, x, y, size, borderColor);
// Inside area
if (RadioButton.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
cross(target, x + 2, y + 2, size - 4, backgroundColor);
}
// "Tick"
if (RadioButton.this.isChecked()) {
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
}
}
}
private boolean checked;
private RadioButtonGroup group = null;
public RadioButton(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
group.setLayoutHint(basicChild.getLayoutHint());
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addListener(KeyEvent.class, e -> {
if (e.isRelease()) return false;
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
if (this.group != null) {
this.group.selectPrevious();
this.group.getSelected().takeFocus();
}
return true;
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
if (this.group != null) {
this.group.selectNext();
this.group.getSelected().takeFocus();
}
return true;
}
return false;
});
addAction(b -> setChecked(true));
}
public RadioButton(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public RadioButton(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public RadioButton(String name, String label) {
this(name, label, false);
}
/**
* @param group the group to set
*/
public RadioButton setGroup(RadioButtonGroup group) {
if (this.group != null) {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button in the group
}
this.group = group;
if (this.group != null) {
group.buttons.add(this);
addAction(group.listener);
}
setChecked(false);
return this;
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of setChecked()
}
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -0,0 +1,119 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
public class RadioButtonGroup {
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
private RadioButton selected = null;
Consumer<BasicButton> listener = b -> {
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
select((RadioButton) b);
}
};
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public List<RadioButton> getButtons() {
return Collections.unmodifiableList(buttons);
}
public synchronized RadioButton getSelected() {
if (!buttons.contains(selected)) {
selected = null;
}
return selected;
}
public synchronized void select(RadioButton button) {
if (button != null && !buttons.contains(button)) {
throw new IllegalArgumentException("Button " + button + " is not in the group");
}
getSelected(); // Clear if invalid
if (selected == button) {
return; // Terminate listener-setter recursion
}
if (selected != null) {
selected.setChecked(false);
}
selected = button;
if (selected != null) {
selected.setChecked(true);
}
actions.forEach(action -> action.accept(this));
}
public void selectNext() {
selectNeighbour(+1);
}
public void selectPrevious() {
selectNeighbour(-1);
}
private synchronized void selectNeighbour(int direction) {
if (getSelected() == null) {
if (buttons.isEmpty()) {
throw new IllegalStateException("Cannot select neighbour button: group empty");
}
select(buttons.get(0));
} else {
RadioButton button;
int index = buttons.indexOf(selected);
do {
index += direction;
if (index >= buttons.size()) {
index = 0;
} else if (index < 0) {
index = buttons.size() - 1;
}
button = buttons.get(index);
} while (button != getSelected() && !button.isEnabled());
select(button);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
public class ButtonEvent extends ComponentEvent {
public static class Press extends ButtonEvent {
public Press(BasicButton button) {
super(button, true);
}
}
public static class Release extends ButtonEvent {
public Release(BasicButton button) {
super(button, false);
}
}
private final boolean isPress;
protected ButtonEvent(BasicButton button, boolean isPress) {
super(button);
this.isPress = isPress;
}
public static ButtonEvent create(BasicButton button, boolean isPress) {
if (isPress) {
return new Press(button);
} else {
return new Release(button);
}
}
public boolean isPress() {
return isPress;
}
public boolean isRelease() {
return !isPress;
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.client.graphics.gui.event;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class EnableEvent extends ComponentEvent {
public EnableEvent(Component component) {
super(component);
}
}

View File

@ -0,0 +1,78 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.layout;
import static java.lang.Math.max;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.Layout;
public class LayoutFill implements Layout {
private final int margin;
public LayoutFill(int margin) {
this.margin = margin;
}
public LayoutFill() {
this(0);
}
@Override
public void layout(Component c) {
c.getChildren().forEach(child -> {
int cWidth = c.getWidth() - 2 * margin;
int cHeight = c.getHeight() - 2 * margin;
child.setBounds(
c.getX() + margin,
c.getY() + margin,
cWidth,
cHeight
);
});
}
@Override
public Vec2i calculatePreferredSize(Component c) {
Vec2i result = new Vec2i(0, 0);
c.getChildren().stream()
.map(child -> child.getPreferredSize())
.forEach(size -> {
result.x = max(size.x, result.x);
result.y = max(size.y, result.y);
});
result.x += 2 * margin;
result.y += 2 * margin;
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + margin + ")";
}
}

View File

@ -97,16 +97,27 @@ public class LayoutGrid implements Layout {
void setBounds(int column, int row, Component child, Component parent) { void setBounds(int column, int row, Component child, Component parent) {
if (!isSummed) if (!isSummed)
throw new IllegalStateException("Not summed yet"); throw new IllegalStateException("Not summed yet");
int width, height;
if (column == columns.length - 1) {
width = parent.getWidth() - margin - columns[column];
} else {
width = columns[column + 1] - columns[column] - gap;
}
if (row == rows.length - 1) {
height = parent.getHeight() - margin - rows[row];
} else {
height = rows[row + 1] - rows[row] - gap;
}
child.setBounds( child.setBounds(
parent.getX() + columns[column], parent.getX() + columns[column],
parent.getY() + rows[row], parent.getY() + parent.getHeight() - (rows[row] + height),
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap) width,
: (parent.getWidth() - margin - columns[column])), height
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
: (parent.getHeight() - margin - rows[row]))
); );
} }
} }
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c); GridDimensions grid = calculateGrid(c);
grid.sum(); grid.sum();
int[] coords;
for (Component child : c.getChildren()) { for (Component child : c.getChildren()) {
coords = (int[]) child.getLayoutHint(); Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c); grid.setBounds(coords.x, coords.y, child, c);
} }
} }
} }
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) { private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions(); GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) { for (Component child : parent.getChildren()) {
coords = (int[]) child.getLayoutHint(); Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize()); result.add(coords.x, coords.y, child.getPreferredSize());
} }
return result; return result;

View File

@ -0,0 +1,117 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.menu;
import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Layout;
import ru.windcorp.progressia.client.graphics.gui.Panel;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
public class MenuLayer extends GUILayer {
private final Component content;
private final Component background;
private final Runnable closeAction = () -> {
GUI.removeLayer(this);
};
public MenuLayer(String name, Component content) {
super(name, new LayoutFill(0));
setCursorPolicy(CursorPolicy.REQUIRE);
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
this.content = content;
background.addChild(content);
getRoot().addChild(background);
}
public MenuLayer(String name, Layout contentLayout) {
this(name, new Panel(name + ".Content", contentLayout));
}
public MenuLayer(String name) {
this(name, new LayoutVertical(20, 10));
}
public Component getContent() {
return content;
}
public Component getBackground() {
return background;
}
protected void addTitle() {
String translationKey = "Layer" + getName() + ".Title";
MutableString titleText = new MutableStringLocalized(translationKey);
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
Label label = new Label(getName() + ".Title", titleFont, titleText);
getContent().addChild(label);
Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
panel.setLayout(new LayoutFill() {
@Override
public Vec2i calculatePreferredSize(Component c) {
return new Vec2i(label.getPreferredSize().x + 40, 4);
}
});
getContent().addChild(panel);
}
protected Runnable getCloseAction() {
return closeAction;
}
@Override
protected void handleInput(Input input) {
if (!input.isConsumed()) {
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
getCloseAction().run();
}
}
}
super.handleInput(input);
input.consume();
}
}

View File

@ -59,6 +59,8 @@ public class LayerWorld extends Layer {
super("World"); super("World");
this.client = client; this.client = client;
this.inputBasedControls = new InputBasedControls(client); this.inputBasedControls = new InputBasedControls(client);
setCursorPolicy(CursorPolicy.FORBID);
} }
@Override @Override

View File

@ -19,6 +19,8 @@
package ru.windcorp.progressia.client.world; package ru.windcorp.progressia.client.world;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener; import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockData;
@ -66,25 +68,33 @@ class ChunkUpdateListener implements ChunkDataListener {
} }
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) { private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = new Vec3i(chunk.getPosition()); Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
if (blockInChunk.x == 0) { checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
chunkPos.x -= 1; checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
} else if (blockInChunk.x == ChunkData.BLOCKS_PER_CHUNK - 1) { checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
chunkPos.x += 1;
} else if (blockInChunk.y == 0) { Vectors.release(chunkPos);
chunkPos.y -= 1; }
} else if (blockInChunk.y == ChunkData.BLOCKS_PER_CHUNK - 1) {
chunkPos.y += 1; private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
} else if (blockInChunk.z == 0) { int block = VectorUtil.get(blockInChunk, axis);
chunkPos.z -= 1; int diff = 0;
} else if (blockInChunk.z == ChunkData.BLOCKS_PER_CHUNK - 1) {
chunkPos.z += 1; if (block == 0) {
diff = -1;
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else { } else {
return; return;
} }
int previousChunkPos = VectorUtil.get(chunkPos, axis);
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
world.markChunkForUpdate(chunkPos); world.markChunkForUpdate(chunkPos);
VectorUtil.set(chunkPos, axis, previousChunkPos);
} }
} }

View File

@ -0,0 +1,31 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.resource;
import java.io.InputStream;
import ru.windcorp.progressia.Progressia;
public class ClasspathResourceReader implements ResourceReader {
@Override
public InputStream read(String name) {
return Progressia.class.getClassLoader().getResourceAsStream(name);
}
}

View File

@ -0,0 +1,36 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FilesystemResourceReader implements ResourceReader {
@Override
public InputStream read(String name) {
try {
return Files.newInputStream(Paths.get(name));
} catch (IOException e) {
return null;
}
}
}

View File

@ -30,19 +30,24 @@ import org.lwjgl.BufferUtils;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.common.util.Named; import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Resource extends Named { public class Resource extends Named {
private final ResourceReader resourceReader;
public Resource(String name) { public Resource(String name, ResourceReader resourceReader) {
super(name); super(name);
this.resourceReader = resourceReader;
} }
public InputStream getInputStream() { public InputStream getInputStream() {
// TODO Do proper resource lookup return getResourceReader().read(getName());
return Progressia.class.getClassLoader().getResourceAsStream(getName()); }
public ResourceReader getResourceReader() {
return resourceReader;
} }
public Reader getReader() { public Reader getReader() {

View File

@ -19,9 +19,16 @@
package ru.windcorp.progressia.common.resource; package ru.windcorp.progressia.common.resource;
public class ResourceManager { public class ResourceManager {
private static final ResourceReader CLASSPATH_READER = new ClasspathResourceReader();
private static final ResourceReader FILESYSTEM_READER = new FilesystemResourceReader();
public static Resource getResource(String name) { public static Resource getResource(String name) {
return new Resource(name); return new Resource(name, CLASSPATH_READER);
}
public static Resource getFileResource(String name) {
return new Resource(name, FILESYSTEM_READER);
} }
public static Resource getTextureResource(String name) { public static Resource getTextureResource(String name) {

View File

@ -0,0 +1,26 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.resource;
import java.io.InputStream;
public interface ResourceReader {
InputStream read(String name);
}

View File

@ -203,13 +203,12 @@ public class CrashReports {
if (provider == null) if (provider == null)
continue; continue;
addSeparator(output);
try { try {
Map<String, String> buf = new HashMap<>(); Map<String, String> buf = new HashMap<>();
provider.provideContext(buf); provider.provideContext(buf);
if (!buf.isEmpty()) { if (!buf.isEmpty()) {
addSeparator(output);
output.append(StringUtil.center(provider.getName(), 80)).append("\n"); output.append(StringUtil.center(provider.getName(), 80)).append("\n");
for (Map.Entry<String, String> entry : buf.entrySet()) { for (Map.Entry<String, String> entry : buf.entrySet()) {
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");

View File

@ -26,12 +26,12 @@ public class RAMContextProvider implements ContextProvider {
@Override @Override
public void provideContext(Map<String, String> output) { public void provideContext(Map<String, String> output) {
output.put("Max Memory", Long.toString(Runtime.getRuntime().maxMemory() / 1024 / 1024) + " MB"); output.put("Max Memory", Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
output.put("Total Memory", Long.toString(Runtime.getRuntime().totalMemory() / 1024 / 1024) + " MB"); output.put("Total Memory", Runtime.getRuntime().totalMemory() / 1024 / 1024 + " MB");
output.put("Free Memory", Long.toString(Runtime.getRuntime().freeMemory() / 1024 / 1024) + " MB"); output.put("Free Memory", Runtime.getRuntime().freeMemory() / 1024 / 1024 + " MB");
output.put( output.put(
"Used Memory", "Used Memory",
Long.toString((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024
+ " MB" + " MB"
); );
} }

View File

@ -0,0 +1,41 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.util.crash.providers;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.common.util.crash.ContextProvider;
import java.util.Map;
public class ScreenContextProvider implements ContextProvider {
@Override
public void provideContext(Map<String, String> output) {
if (GraphicsBackend.isGLFWInitialized()) {
output.put("Refresh rate", GraphicsBackend.getRefreshRate() + " Hz");
output.put("Size", GraphicsBackend.getFrameWidth() + "x" + GraphicsBackend.getFrameHeight());
output.put("Fullscreen", GraphicsBackend.isFullscreen() ? "enabled" : "disabled");
}
}
@Override
public String getName() {
return "Screen Context Provider";
}
}

View File

@ -207,6 +207,18 @@ public class Server {
return this.serverThread.getTicker().getTPS(); return this.serverThread.getTicker().getTPS();
} }
/**
* Returns the amount of ticks performed since the server has started. This
* value resets on shutdowns. The counter is incremented at the end of a
* tick.
*
* @return the number of times the world has finished a tick since the
* server has started.
*/
public long getUptimeTicks() {
return this.serverThread.getTicker().getUptimeTicks();
}
/** /**
* Returns the {@link WorldAccessor} object for this server. Use the * Returns the {@link WorldAccessor} object for this server. Use the
* provided accessor to request common {@link Evaluation}s and * provided accessor to request common {@link Evaluation}s and

View File

@ -82,6 +82,7 @@ public class TickerCoordinator {
private boolean isTickStartSet = false; private boolean isTickStartSet = false;
private long tickStart = -1; private long tickStart = -1;
private double tickLength = 1.0 / 20; // Do something about it private double tickLength = 1.0 / 20; // Do something about it
private long ticks = 0;
private final Logger logger = LogManager.getLogger("Ticker Coordinator"); private final Logger logger = LogManager.getLogger("Ticker Coordinator");
@ -151,6 +152,10 @@ public class TickerCoordinator {
public double getTPS() { public double getTPS() {
return 1 / tickLength; return 1 / tickLength;
} }
public long getUptimeTicks() {
return ticks;
}
private void onTickStart() { private void onTickStart() {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@ -163,6 +168,10 @@ public class TickerCoordinator {
tickStart = System.currentTimeMillis(); tickStart = System.currentTimeMillis();
} }
private void onTickEnd() {
ticks++;
}
/* /*
* runOneTick & Friends * runOneTick & Friends
@ -182,6 +191,8 @@ public class TickerCoordinator {
logger.debug("Pass complete"); logger.debug("Pass complete");
passes++; passes++;
} }
onTickEnd();
logger.debug("Tick complete; run {} passes", passes); logger.debug("Tick complete; run {} passes", passes);
@ -191,7 +202,7 @@ public class TickerCoordinator {
// ...or almost silently // ...or almost silently
logger.debug("Tick interrupted. WTF?"); logger.debug("Tick interrupted. WTF?");
} catch (Exception e) { } catch (Exception e) {
crash(e, "Coordinator"); throw CrashReports.report(e, "Coordinator");
} }
} }

View File

@ -23,7 +23,7 @@ import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typeface; import ru.windcorp.progressia.client.graphics.font.Typeface;
import ru.windcorp.progressia.client.graphics.gui.GUILayer; import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label; import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Panel; import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.MutableStringLocalized; import ru.windcorp.progressia.client.localization.MutableStringLocalized;
@ -33,12 +33,12 @@ public class LayerAbout extends GUILayer {
public LayerAbout() { public LayerAbout() {
super("LayerAbout", new LayoutAlign(1, 1, 5)); super("LayerAbout", new LayoutAlign(1, 1, 5));
Panel panel = new Panel("ControlDisplays", new LayoutVertical(5)); Group group = new Group("ControlDisplays", new LayoutVertical(5));
Font font = new Font().withColor(Colors.WHITE).deriveOutlined().withAlign(Typeface.ALIGN_RIGHT); Font font = new Font().withColor(Colors.WHITE).deriveOutlined().withAlign(Typeface.ALIGN_RIGHT);
Font aboutFont = font.withColor(0xFF37A3E6).deriveBold(); Font aboutFont = font.withColor(0xFF37A3E6).deriveBold();
panel.addChild( group.addChild(
new Label( new Label(
"About", "About",
aboutFont, aboutFont,
@ -46,7 +46,7 @@ public class LayerAbout extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"Version", "Version",
font, font,
@ -54,7 +54,7 @@ public class LayerAbout extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"DebugHint", "DebugHint",
font, font,
@ -62,7 +62,7 @@ public class LayerAbout extends GUILayer {
) )
); );
getRoot().addChild(panel); getRoot().addChild(group);
} }

View File

@ -0,0 +1,70 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.Checkbox;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.RadioButton;
import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup;
import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer;
public class LayerButtonTest extends MenuLayer {
public LayerButtonTest() {
super("ButtonTest");
addTitle();
Button blockableButton;
getContent().addChild((blockableButton = new Button("BlockableButton", "Blockable")).addAction(b -> {
System.out.println("Button Blockable!");
}));
blockableButton.setEnabled(false);
getContent().addChild(new Checkbox("EnableButton", "Enable").addAction(b -> {
blockableButton.setEnabled(((Checkbox) b).isChecked());
}));
RadioButtonGroup group = new RadioButtonGroup().addAction(g -> {
System.out.println("RBG! " + g.getSelected().getLabel().getCurrentText());
});
getContent().addChild(new RadioButton("RB1", "Moon").setGroup(group));
getContent().addChild(new RadioButton("RB2", "Type").setGroup(group));
getContent().addChild(new RadioButton("RB3", "Ice").setGroup(group));
getContent().addChild(new RadioButton("RB4", "Cream").setGroup(group));
getContent().getChild(getContent().getChildren().size() - 1).setEnabled(false);
getContent().addChild(new Label("Hint", new Font().withColor(Colors.LIGHT_GRAY), "This is a MenuLayer"));
getContent().addChild(new Button("Continue", "Continue").addAction(b -> {
getCloseAction().run();
}));
getContent().addChild(new Button("Quit", "Quit").addAction(b -> {
System.exit(0);
}));
getContent().takeFocus();
}
}

View File

@ -18,22 +18,18 @@
package ru.windcorp.progressia.test; package ru.windcorp.progressia.test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import glm.vec._4.Vec4; import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel; import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
import ru.windcorp.progressia.client.graphics.gui.GUILayer; import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label; import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Panel; import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.client.localization.Localizer;
@ -44,19 +40,24 @@ import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
public class LayerTestGUI extends GUILayer { public class LayerTestGUI extends GUILayer {
public LayerTestGUI() { public LayerTestGUI() {
super("LayerTestGui", new LayoutAlign(0, 1, 5)); super("LayerTestGui", new LayoutAlign(0, 1, 5));
Panel panel = new Panel("ControlDisplays", new LayoutVertical(5)); Group group = new Group("ControlDisplays", new LayoutVertical(5));
Vec4 color = Colors.WHITE; Vec4 color = Colors.WHITE;
Font font = new Font().withColor(color).deriveOutlined(); Font font = new Font().withColor(color).deriveOutlined();
TestPlayerControls tpc = TestPlayerControls.getInstance(); TestPlayerControls tpc = TestPlayerControls.getInstance();
panel.addChild( group.addChild(
new Label( new Label(
"IsFlyingDisplay", "IsFlyingDisplay",
font, font,
@ -64,7 +65,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"IsSprintingDisplay", "IsSprintingDisplay",
font, font,
@ -72,15 +73,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label(
"IsMouseCapturedDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsMouseCapturedDisplay", tpc::isMouseCaptured)
)
);
panel.addChild(
new Label( new Label(
"CameraModeDisplay", "CameraModeDisplay",
font, font,
@ -91,7 +84,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"LanguageDisplay", "LanguageDisplay",
font, font,
@ -99,7 +92,23 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label(
"FullscreenDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
)
);
group.addChild(
new Label(
"VSyncDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
)
);
group.addChild(
new DynamicLabel( new DynamicLabel(
"FPSDisplay", "FPSDisplay",
font, font,
@ -111,7 +120,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new DynamicLabel( new DynamicLabel(
"TPSDisplay", "TPSDisplay",
font, font,
@ -120,7 +129,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new DynamicLabel( new DynamicLabel(
"ChunkUpdatesDisplay", "ChunkUpdatesDisplay",
font, font,
@ -132,7 +141,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new DynamicLabel( new DynamicLabel(
"PosDisplay", "PosDisplay",
font, font,
@ -141,7 +150,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"SelectedBlockDisplay", "SelectedBlockDisplay",
font, font,
@ -152,7 +161,7 @@ public class LayerTestGUI extends GUILayer {
) )
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"SelectedTileDisplay", "SelectedTileDisplay",
font, font,
@ -163,7 +172,7 @@ public class LayerTestGUI extends GUILayer {
) )
) )
); );
panel.addChild( group.addChild(
new Label( new Label(
"PlacementModeHint", "PlacementModeHint",
font, font,
@ -171,7 +180,7 @@ public class LayerTestGUI extends GUILayer {
) )
); );
getRoot().addChild(panel); getRoot().addChild(group);
} }
public Runnable getUpdateCallback() { public Runnable getUpdateCallback() {

View File

@ -32,7 +32,7 @@ import org.lwjgl.glfw.GLFW;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.SoundEffect; import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.comms.controls.*; import ru.windcorp.progressia.client.comms.controls.*;
import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher; import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
@ -289,6 +289,15 @@ public class TestContent {
) )
); );
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived)); logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived));
triggers.register(
ControlTriggers.localOf(
"Test:StartNextMusic",
KeyEvent.class,
TestMusicPlayer::startNextNow,
KeyMatcher.of(GLFW.GLFW_KEY_M).matcher()
)
);
} }
private static void register(BlockData x) { private static void register(BlockData x) {
@ -362,7 +371,7 @@ public class TestContent {
private static void onBlockBreakTrigger(ControlData control) { private static void onBlockBreakTrigger(ControlData control) {
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock()); ((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
SoundEffect sfx = new SoundEffect("Progressia:BlockDestroy"); Sound sfx = new Sound("Progressia:BlockDestroy");
sfx.setPosition(getSelection().getPoint()); sfx.setPosition(getSelection().getPoint());
sfx.setPitch((float) (Math.random() + 1 * 0.5)); sfx.setPitch((float) (Math.random() + 1 * 0.5));
sfx.play(false); sfx.play(false);

View File

@ -0,0 +1,152 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.client.audio.AudioFormat;
import ru.windcorp.progressia.client.audio.AudioManager;
import ru.windcorp.progressia.client.audio.AudioRegistry;
import ru.windcorp.progressia.client.audio.Music;
import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class TestMusicPlayer implements Runnable {
private static final int MIN_SILENCE = 15 * 1000; // 15 seconds
private static final int MAX_SILENCE = 60 * 1000; // one minute
private static TestMusicPlayer instance = null;
private final List<SoundType> compositions = new ArrayList<>();
private final Random random = new Random();
private long nextStart;
private Sound lastStarted = null;
public TestMusicPlayer() {
this.nextStart = System.currentTimeMillis();
instance = this;
}
public static void start() {
Thread thread = new Thread(new TestMusicPlayer(), "Music Thread");
thread.setDaemon(true);
thread.start();
}
@Override
public void run() {
loadCompositions();
if (compositions.isEmpty()) {
LogManager.getLogger().warn("No music found");
return;
}
while (true) {
try {
synchronized (this) {
while (true) {
long now = System.currentTimeMillis();
if (nextStart > now) {
wait(nextStart - now);
} else {
break;
}
}
}
} catch (InterruptedException e) {
LogManager.getLogger().warn("Received interrupt in music thread, terminating thread...");
return;
}
startNextComposition();
}
}
private void loadCompositions() {
try {
Path directory = Paths.get("music");
if (!Files.isDirectory(directory)) {
Files.createDirectories(directory);
}
Iterator<Path> it = Files.walk(directory).filter(Files::isRegularFile).iterator();
int i = 0;
while (it.hasNext()) {
String file = it.next().toString();
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
}
String id = "Progressia:Music" + (i++);
AudioManager.loadSound(ResourceManager.getFileResource(file.toString()), id, AudioFormat.STEREO);
SoundType composition = AudioRegistry.getInstance().get(id);
compositions.add(composition);
LogManager.getLogger().info("Loaded " + file);
}
} catch (IOException e) {
throw CrashReports.report(e, "Could not load music");
}
}
private synchronized void startNextComposition() {
int index = random.nextInt(compositions.size());
SoundType composition = compositions.get(index);
long now = System.currentTimeMillis();
long durationInMs = (long) (composition.getDuration() * 1000);
long silence = random.nextInt(MAX_SILENCE - MIN_SILENCE) + MIN_SILENCE;
nextStart = now + durationInMs + silence;
lastStarted = new Music(composition);
lastStarted.play(false);
}
public static void startNextNow() {
if (instance == null) return;
synchronized (instance) {
instance.nextStart = System.currentTimeMillis();
instance.notifyAll();
}
}
}

View File

@ -84,8 +84,6 @@ public class TestPlayerControls {
private double lastSpacePress = Double.NEGATIVE_INFINITY; private double lastSpacePress = Double.NEGATIVE_INFINITY;
private double lastSprintPress = Double.NEGATIVE_INFINITY; private double lastSprintPress = Double.NEGATIVE_INFINITY;
private boolean captureMouse = true;
private int selectedBlock = 0; private int selectedBlock = 0;
private int selectedTile = 0; private int selectedTile = 0;
private boolean isBlockSelected = true; private boolean isBlockSelected = true;
@ -203,9 +201,24 @@ public class TestPlayerControls {
case GLFW.GLFW_KEY_ESCAPE: case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress()) if (!event.isPress())
return false; return false;
handleEscape(); handleEscape();
break; break;
case GLFW.GLFW_KEY_F11:
if (!event.isPress())
return false;
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
updateGUI();
break;
case GLFW.GLFW_KEY_F12:
if (!event.isPress())
return false;
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
updateGUI();
break;
case GLFW.GLFW_KEY_F3: case GLFW.GLFW_KEY_F3:
if (!event.isPress()) if (!event.isPress())
return false; return false;
@ -298,14 +311,10 @@ public class TestPlayerControls {
} }
private void handleEscape() { private void handleEscape() {
if (captureMouse) { movementForward = 0;
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); movementRight = 0;
} else { movementUp = 0;
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); GUI.addTopLayer(new LayerButtonTest());
}
captureMouse = !captureMouse;
updateGUI();
} }
private void handleDebugLayerSwitch() { private void handleDebugLayerSwitch() {
@ -344,10 +353,6 @@ public class TestPlayerControls {
} }
private void onMouseMoved(CursorMoveEvent event) { private void onMouseMoved(CursorMoveEvent event) {
if (!captureMouse) {
return;
}
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return; return;
} }
@ -445,10 +450,6 @@ public class TestPlayerControls {
return isSprinting; return isSprinting;
} }
public boolean isMouseCaptured() {
return captureMouse;
}
public BlockData getSelectedBlock() { public BlockData getSelectedBlock() {
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock); return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
} }

View File

@ -6,7 +6,6 @@ LayerAbout.DebugHint = Debug GUI: F3
LayerTestGUI.IsFlyingDisplay = Flying: %5s (Space bar x2) LayerTestGUI.IsFlyingDisplay = Flying: %5s (Space bar x2)
LayerTestGUI.IsSprintingDisplay = Sprinting: %5s (W x2) LayerTestGUI.IsSprintingDisplay = Sprinting: %5s (W x2)
LayerTestGUI.IsMouseCapturedDisplay = Mouse captured: %5s (Esc)
LayerTestGUI.CameraModeDisplay = Camera mode: %5d (F5) LayerTestGUI.CameraModeDisplay = Camera mode: %5d (F5)
LayerTestGUI.LanguageDisplay = Language: %5s (L) LayerTestGUI.LanguageDisplay = Language: %5s (L)
LayerTestGUI.FPSDisplay = FPS: LayerTestGUI.FPSDisplay = FPS:
@ -18,4 +17,8 @@ LayerTestGUI.PosDisplay.NA.Client = Pos: client n/a
LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a
LayerTestGUI.SelectedBlockDisplay = %s Block: %s LayerTestGUI.SelectedBlockDisplay = %s Block: %s
LayerTestGUI.SelectedTileDisplay = %s Tile: %s LayerTestGUI.SelectedTileDisplay = %s Tile: %s
LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel) LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
LayerTestGUI.IsVSync = VSync: %5s (F12)
LayerButtonTest.Title = Button Test

View File

@ -6,7 +6,6 @@ LayerAbout.DebugHint = Отладочный GUI: F3
LayerTestGUI.IsFlyingDisplay = Полёт: %5s (Пробел x2) LayerTestGUI.IsFlyingDisplay = Полёт: %5s (Пробел x2)
LayerTestGUI.IsSprintingDisplay = Бег: %5s (W x2) LayerTestGUI.IsSprintingDisplay = Бег: %5s (W x2)
LayerTestGUI.IsMouseCapturedDisplay = Захват мыши: %5s (Esc)
LayerTestGUI.CameraModeDisplay = Камера: %5d (F5) LayerTestGUI.CameraModeDisplay = Камера: %5d (F5)
LayerTestGUI.LanguageDisplay = Язык: %5s (L) LayerTestGUI.LanguageDisplay = Язык: %5s (L)
LayerTestGUI.FPSDisplay = FPS: LayerTestGUI.FPSDisplay = FPS:
@ -18,4 +17,8 @@ LayerTestGUI.PosDisplay.NA.Client = Поз: клиент н/д
LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д
LayerTestGUI.SelectedBlockDisplay = %s Блок: %s LayerTestGUI.SelectedBlockDisplay = %s Блок: %s
LayerTestGUI.SelectedTileDisplay = %s Плитка: %s LayerTestGUI.SelectedTileDisplay = %s Плитка: %s
LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка) LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
LayerButtonTest.Title = Тест Кнопок