ONE Java codebase for Web, Desktop and Mobile Apps. Supports Android, iOS, Windows, Linux, MacOS.
OTHER License
Java Framework for developing low-code Desktop and Mobile Applications in one codebase with Java. Using Web-Tech (JS/HTML/CSS) under the hood. Click here for Maven/Gradle/Sbt/Leinigen instructions (Java 11 or higher required).
Mainly backend developers that want to code their frontend/GUI directly in Java in a low-code, fast and pain-less way. In addition, it is also highly beginner-friendly, making it accessible to everyone that is new to coding due to its simplicity.
vertical().add(text("Hello World!"))
import com.osiris.desku.App;
import static com.osiris.desku.Statics;
public class Main {
public static void main(String[] args) throws Exception {
App.uis = new DesktopUIManager(); // Not needed when using the Desku-Starter-App
App.name = "My-App";
App.init();
App.uis.create(() -> {
return vertical().add(text("Hello World!"));
});
}
}
import com.osiris.desku.App;
import static com.osiris.desku.Statics; // Low-code Java UI via static methods
public class Main {
public static void main(String[] args) throws Exception {
// Setup app details and init.
App.uis = new DesktopUIManager(); // Not needed when using the Desku-Starter-App
App.name = "My-App";
App.init();
// Create routes.
// This is only for demonstration.
// Normally you'd create a new class and extend Route instead.
// In the "3 lines example" a new random Route is created in App.uis.create().
// All routes names/paths must start with a "/".
Route home = new MRoute("/", () -> {
return vertical().add(text("Hello World!")); // Low-code Java UI via static methods
});
// Create and show the window, with the content
// loaded by the provided route, in this case "home".
// The content is a tree of components which gets "translated" into HTML
// and then displayed by the users default webview.
App.uis.create(home);
}
}
AppTest home page (24.08.2023), which includes all default components:
Desku is released under a modified MIT license (see full license for details):
If your project generates more than 1000€ a month in revenue and uses this software, you are required to pay a royalty fee to Osiris Team. The royalty fee is 5% of gross revenue (i.e., the total revenue generated by your project). You must make a monthly payment to [email protected] over PayPal. Other ways of payment are possible, contact the above email for further information.
A list of all available extensions can be found here. It can be a single component or a complete suite of multiple components, either way, its pretty easy to create a Desku-Extension:
The base features are implemented, however the default UI component library still needs components, so feel free to contribute if you find something missing.
Todo:
Contributions are welcome! Especially HTML5 component integrations, aka porting an HTML5 component to a Java Desku component.
When building remember to include this specific test, to also update
the Statics
class.
./gradlew build :test --tests "com.osiris.desku.GenerateStatics"
Copy and send this to ChatGPT and ask it questions to get quick support.
The theme can be changed quite easily by setting
the App.theme
variable.
Create your own themes by extending the Theme
class
where you modify existing attributes or add new ones
and update the App.theme
variable.
Probably the best and easiest way to show is with an example. The code below shows the JavaScript click event being implemented:
public class ClickEvent extends JavaScriptEvent {
public final boolean isTrusted;
public final int screenX, screenY;
public ClickEvent(String rawJSMessage, Component<?> comp) {
super(rawJSMessage, comp);
this.isTrusted = jsMessage.get("isTrusted").getAsBoolean();
this.screenX = jsMessage.get("screenX").getAsInt();
this.screenY = jsMessage.get("screenY").getAsInt();
}
}
public class MyComp extends Component<MyComp> {
/**
* Do not add actions via this variable, use {@link #onClick(Consumer)} instead.
*/
public final Event<ClickEvent> _onClick = new Event<>();
public MyComp() {
super("my-comp");
}
/**
* Adds a listener that gets executed when this component was clicked.
*/
public MyComp onClick(Consumer<ClickEvent> code) {
_onClick.addAction((event) -> code.accept(event));
Component<?> _this = this;
UI.current().registerJSListener("click", _this, (msg) -> {
_onClick.execute(new ClickEvent(msg, _this)); // Executes all listeners
});
return target;
}
}
You can register listeners on any JavaScript event you'd like: https://developer.mozilla.org/en-US/docs/Web/Events
Get the components' HTML string via
component.element.outerHTML()
.
Note that this also includes all its children.
To make sure it equals the actual in memory representation
call component.updateAll()
before retrieving the HTML.
The problem in more detail:
public class A extends Component<A>{
// ...
public A methodInA(){
return this;
}
}
public class B extends A{
// ...
public B methodInB(){
return this;
}
}
public class Main{
public void main(){
new B().methodInA(); // If we want to do method chaining, aka access
// another method of class B, its not possible anymore
// due to Java language limits, since now the returned value is of type A.
}
}
Instead of extending classes we are forced (if we want to provide method chaining) to add the super class as field of our current class and wrap around important methods, like so:
public class B extends Component<B>{ // Instead of extending A
public final A a = new A();
public B(){
super("b");
add(a); // Add as child
}
// ...
public B methodInA(){
a.methodInA();
return this;
}
}
I find it easiest to use jSQL-Gen (also developed by me), which generates the Java source code that is needed to interact with your database and solve this issue in a low-code fashion. Note that your database can be integrated in your app / exist on the client directly (via mariaBD4J for example) or hosted by yourself on your server.
(TODO) If you want to store data in the local storage of the clients' browser/webview, you can use ui.localStorage which is the Java implementation of localStorage. Note that the data here is specific to a window/UI/Route, which means that its not shared across them.
For logging, you can use the AL
class and its static methods info/debug, warn and error.
These are pre-formatted with ANSI colors, info is white, debug dark gray,
warn yellow and error red. Colors are stripped when writing to files and the formatting is slightly different.
You can pass Exceptions to warn and error. The stacktrace (plus all the causes) will then be displayed. Note that warn only shows the Exceptions' message in the console. The full stacktrace can only be seen in the log file, by default.
When using error your app will exit in the next 10 seconds, thus you should use it only if the occurred Exception is critical and hinders your app from running.
Note that debug will only be shown in the log file by default, not the console.
This is part of the jlib library, which contains some more useful things you might want to check out.
TODO: Remove older logs to save space on the users' device.
By default, build tools will remove anything that is not a .java source file,
thus your .css/.js or any other files will not be included in the final binary.
Here is how you can fix this in Gradle, simply append the below to your build.gradle
file:
// Ensure that everything other than classes/.java files are also included in the final jar
// This should also be included in your project if you want to easily load resources.
sourceSets {
main {
resources {
srcDirs = ["src/main/java", "src/main/resources"]
include '**/*' // Include everything (no .java by default)
}
}
}
// This must also be included if you want to generate the sources jar without issues
tasks.withType(Jar).configureEach { duplicatesStrategy = DuplicatesStrategy.EXCLUDE }
UI and UIManager are both abstract classes that can be extended. Desku already provides implementations (DesktopUI and DesktopUIManager) via WebView to be able to run on Desktop platforms like Windows, Linux and Mac.
The Desku-Starter-App contains implementations for Android and iOS. If you want to support even more platforms make a pull-request with your implementation!
comp.getValue() should NEVER return null, even if comp.setValue(null) was called before. In that case the default value is returned.
comp.setValue(null) is allowed to ensure compatibility with similar frameworks like JavaFX, and allow some sort of "clearing"/"resetting" of the value, without needing to rely on the error-prone null!
This also means that all components should have a sensible default value (except very basic containers, these use the NoValue.class).