First, add the latest version of JWM specific to your platform to the dependencies:
Key | Value |
---|---|
groupId | io.github.humbleui |
artifactId | jwm |
version |
E.g. for Maven it’ll look like this:
<dependency>
<groupId>io.github.humbleui</groupId>
<artifactId>jwm</artifactId>
<version>0.4.8</version>
</dependency>
Initialize the JWM library, doing operations on the correct thread:
App.start(() -> {
// Initialization here
});
Create a window:
// Inside App::start callback
Window window = App.makeWindow();
window.setTitle("Hello, world!");
Set up a listener:
class EventHandler implements Consumer<Event> {
public final Window window;
public EventHandler(Window window) {
this.window = window;
}
}
Write the accept
function:
@Override
public void accept(Event e) {
System.out.println(e);
if (e instanceof EventWindowCloseRequest) {
window.close();
App.terminate();
}
}
Assign handler to the window:
window.setEventListener(new EventHandler(window));
Display the window:
window.setVisible(true);
And, when the App::start
callback returns, the event loop will start.
See it all together in GettingStarted.java.
On Windows and Linux, just run:
java -cp jwm-0.4.8.jar:types-0.1.1.jar GettingStarted.java
types-0.1.1.jar
here are from https://github.com/HumbleUI/Types. They will be included as a transitive dependency if you are using Maven or Gradle.
You can access information about screen configuration through App::getScreens
and App::getPrimaryScreen
.
This will create a window that occupies the left side of the screen and is 800 px wide:
Screen screen = App.getPrimaryScreen();
IRect workArea = screen.getWorkArea();
window.setWindowPosition(workArea.getLeft(), workArea.getTop());
float scale = screen.getScale();
window.setWindowSize(800 * scale, workArea.getHeight());
All screen coordinates are in one absolute coordinate space. Top left corner of the primary screen usually has (0, 0) coordinate:
All pixel sizes are also unscaled. They correspond to the physical screen pixels, not “logical” pixels. Note: this is similar to how Windows/Linux work but opposite of what macOS does. JWM abstracts that difference away for you.
To convert “logical” pixels to physical ones, multiply by Screen::getScale()
. E.g. if you want 800×600 window on macOS, do:
float scale = window.getScreen().getScale();
window.setWindowSize(800 * scale, 600 * scale);
Window position is specified in the same absolute coordinate space. E.g. to move window to another screen just do Window::setPosition
to a coordinate within that screen bounds:
@Override
public void accept(Event e) {
if (e instanceof EventMouseMove ee) {
// ee.getX() / ee.getY() for window-relative coordinates
} else if (e instanceof EventMouseScroll ee) {
// ee.getDeltaX() / ee.getDeltaY() for amount of text lines to scroll
// See #115
} else if (e instanceof EventMouseButton ee) {
// ee.getButton() + ee.getPressed()
}
}
Simple key handling (e.g. for program shortcuts) via EventKey
.
Simple text input via EventTextInput
.
Advanced text input (IME) via EventTextInputMarked
+ TextInputClient
. See PanelTextInput.java for hints how to handle IME input.
@Override
public void accept(Event e) {
if (e instanceof EventKey ee) {
// ee.getKey() + ee.isPressed() for raw keyboard keys
// Do not use this for text input
} else if (e instanceof EventTextInput ee) {
// ee.getText() for text string entered
} else if (e instanceof EventTextInputMarked ee) {
// ...
}
}
JWM drawing loop is on-demand and always v-synced with monitor refresh rate (except rare cases like raster rendering on X11). To draw a frame, you first post a frame request:
window.requestFrame();
Note that if you do it not from the UI thread (== not from accept
method of EventHandler), use App::runOnUIThread
to get to UI thread first:
App.runOnUIThread(() -> window.requestFrame());
Then, in accept
method handle EventFrame
:
@Override
public void accept(Event e) {
if (e instanceof EventFrame) {
paint(window);
}
}
This will render just one frame. If you want to render in a loop (e.g. for an animation or for a game), you can request next frame right after paint:
@Override
public void accept(Event e) {
if (e instanceof EventFrame) {
paint(window);
window.requestFrame();
}
}
Now, JWM does not provide anything to actually draw stuff. You can use other libraries, e.g. Skija, to do that. But JWM provides Layers to initilize graphical context and get all the necessary pointers to use with DirectX 12, OpenGL or Metal.
Let’s write a simple paint
function that initializes OpenGL and uses it to draw. First, let’s create a layer and attach
it:
class EventHandler implements Consumer<Event> {
public final Window window;
public final LayerGL layer;
public EventHandler(Window window) {
this.window = window;
layer = new LayerGL();
window.setLayer(layer);
}
}
Then, if the paint request comes, we need to redirect it to the paint function:
@Override
public void accept(Event e) {
if (e instanceof EventFrame) {
paint();
window.requestFrame();
}
}
And finally, we write the paint function itself:
public void paint() {
layer.makeCurrent();
// do the drawing
layer.swapBuffers();
}
If you wish to use Skija for rendering, JWM comes with convenient Layer*Skija
classes:
layer = new LayerGLSkija();
window.setLayer(layer);
Skija layers work by generating a special event (EventFrameSkija
) every time frame is requested. So instead of handling EventFrame
, handle EventFrameSkija
:
if (e instanceof EventFrameSkija ee) {
Surface s = ee.getSurface();
paint(s.getCanvas(), s.getWidth(), s.getHeight());
}
Note: JWM does not declare Skija as a dependency. For Skija layers to work, add Skija (0.98.0 or later) to your apps’s dependencies.