├── .gitignore ├── Browser ├── README.md ├── browser.png ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── browser │ └── Browser.java ├── Calculator ├── README.md ├── build.gradle.kts ├── calculator.png ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── Main.kt │ └── resources │ └── Calculator.css ├── CodeEditor ├── README.md ├── build.gradle ├── codeeditor.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── codeeditor │ ├── CodeEditor.java │ └── EditorWindow.java ├── ColumnViewDatagrid ├── README.md ├── build.gradle ├── columnview.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── columnview │ └── ColumnViewDatagrid.java ├── GettingStarted ├── README.md ├── example-0 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── Example0.java ├── example-1 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── Example1.java ├── example-2 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── Example2.java ├── example-3 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── Example3.java ├── example-4 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── Example4.java │ │ └── resources │ │ └── builder.ui ├── example-5-part1 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java ├── example-5-part2 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ └── window.ui ├── example-5-part3 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ └── window.ui ├── example-5-part4 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ ├── gears-menu.ui │ │ └── window.ui ├── example-5-part6 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppPrefs.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ ├── gears-menu.ui │ │ ├── org.gtk.exampleapp.gschema.xml │ │ ├── prefs.ui │ │ └── window.ui ├── example-5-part7 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppPrefs.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ ├── gears-menu.ui │ │ ├── org.gtk.exampleapp.gschema.xml │ │ ├── prefs.ui │ │ └── window.ui ├── example-5-part8 │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ ├── ExampleApp.java │ │ ├── ExampleAppPrefs.java │ │ ├── ExampleAppWindow.java │ │ └── ExampleMainClass.java │ │ └── resources │ │ ├── exampleapp.gresource.xml │ │ ├── gears-menu.ui │ │ ├── org.gtk.exampleapp.gschema.xml │ │ ├── prefs.ui │ │ └── window.ui └── example-5-part9 │ ├── build.gradle │ └── src │ └── main │ ├── java │ ├── ExampleApp.java │ ├── ExampleAppPrefs.java │ ├── ExampleAppWindow.java │ └── ExampleMainClass.java │ └── resources │ ├── exampleapp.gresource.xml │ ├── gears-menu.ui │ ├── org.gtk.exampleapp.gschema.xml │ ├── prefs.ui │ └── window.ui ├── HelloTemplate ├── README.md ├── build.gradle ├── flatpak │ └── my.example.HelloTemplate.json ├── settings.gradle ├── src │ └── main │ │ ├── java │ │ └── my │ │ │ └── example │ │ │ └── hellotemplate │ │ │ ├── HelloApplication.java │ │ │ ├── HelloWindow.java │ │ │ └── Main.java │ │ └── resources │ │ ├── gtk │ │ └── help-overlay.ui │ │ ├── helloworld.gresource.xml │ │ └── window.ui └── template-helloworld.png ├── HelloWorld ├── README.md ├── build.gradle ├── simple-helloworld.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── helloworld │ └── HelloWorld.java ├── HelloWorldScala ├── README.md ├── build.sbt ├── simple-helloworld.png └── src │ └── main │ └── scala │ └── HelloWorld.scala ├── ImageViewer ├── README.md ├── build.sbt ├── image-viewer.png └── src │ └── main │ └── scala │ ├── App.scala │ ├── Main.scala │ └── Window.scala ├── Javascript ├── README.md ├── build.gradle ├── javascript.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── javascript │ └── JavascriptExample.java ├── LICENSE ├── ListViewer ├── README.md ├── build.gradle ├── listviewer.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── listviewer │ └── ListViewer.java ├── Logging ├── README.md ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── logging │ ├── Logging.java │ └── SLF4JLogWriterFunc.java ├── MediaStream ├── README.md ├── build.gradle ├── mediastream.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── mediastream │ └── Animation.java ├── Notepad ├── README.md ├── build.gradle ├── notepad.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── notepad │ ├── EditorWindow.java │ └── Notepad.java ├── OpenGL ├── README.md ├── build.gradle.kts ├── opengl.png ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── jwharm │ └── javagi │ └── examples │ ├── Main.kt │ └── renderers │ ├── GLESRenderer.kt │ ├── GLRenderer.kt │ └── Renderer.kt ├── PegSolitaire ├── README.md ├── build.gradle ├── peg-solitaire.png └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── pegsolitaire │ └── PegSolitaire.java ├── PlaySound ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── github │ │ └── jwharm │ │ └── javagi │ │ └── examples │ │ └── playsound │ │ └── PlaySound.java │ └── resources │ └── example.ogg ├── README.md ├── ScreenRecorder ├── README.md ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── github │ └── jwharm │ └── javagi │ └── examples │ └── screenrec │ └── ScreenRecorder.java └── Spacewar ├── LICENSE ├── README.md ├── build.gradle.kts ├── settings.gradle.kts ├── spacewar.png └── src └── main └── kotlin └── Spacewar.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Compiled gresource file 5 | *.gresource 6 | 7 | # Log file 8 | *.log 9 | 10 | # BlueJ files 11 | *.ctxt 12 | 13 | # Eclipse files 14 | .classpath 15 | .project 16 | .settings/ 17 | 18 | # Mobile Tools for Java (J2ME) 19 | .mtj.tmp/ 20 | 21 | # Package Files # 22 | *.jar 23 | *.war 24 | *.nar 25 | *.ear 26 | *.zip 27 | *.tar.gz 28 | *.rar 29 | 30 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 31 | hs_err_pid* 32 | replay_pid* 33 | 34 | .gradle/ 35 | build/ 36 | **/.idea 37 | **/gradle* 38 | 39 | # SBT build files 40 | project/ 41 | target/ 42 | 43 | # Recording from the screen recorder example 44 | ScreenRecorder/recording.ogg 45 | -------------------------------------------------------------------------------- /Browser/README.md: -------------------------------------------------------------------------------- 1 | ## Browser 2 | 3 | This example is a simple web browser application based on WebkitGtk and LibAdwaita. It was inspired by the browser example in GNOME Workbench. 4 | 5 | To run the example, clone the repository, navigate to the `Browser` folder, and execute `gradle run`. 6 | 7 | ![Web Browser screenshot](browser.png) 8 | -------------------------------------------------------------------------------- /Browser/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/Browser/browser.png -------------------------------------------------------------------------------- /Browser/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:adw:0.12.2' 11 | implementation 'io.github.jwharm.javagi:webkit:0.12.2' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += '--enable-native-access=ALL-UNNAMED' 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.browser.Browser" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Browser/src/main/java/io/github/jwharm/javagi/examples/browser/Browser.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.browser; 2 | 3 | import org.gnome.adw.Application; 4 | import org.gnome.adw.ApplicationWindow; 5 | import org.gnome.adw.Bin; 6 | import org.gnome.adw.HeaderBar; 7 | import org.gnome.gio.ApplicationFlags; 8 | import org.gnome.glib.GLib; 9 | import org.gnome.glib.Uri; 10 | import org.gnome.gobject.BindingFlags; 11 | import org.gnome.gtk.*; 12 | import org.gnome.webkit.WebView; 13 | 14 | /** 15 | * This class demonstrates a simple WebKitGtk-based browser application 16 | */ 17 | public class Browser { 18 | 19 | // Home page URL 20 | private static final String HOME_PAGE = "https://www.gnome.org/"; 21 | 22 | private final Application app; 23 | 24 | // When loading=true, the refresh button is changed to a stop button, and vice versa 25 | private boolean loading = false; 26 | 27 | // Launch the app 28 | public static void main(String[] args) { 29 | new Browser(args); 30 | } 31 | 32 | public Browser(String[] args) { 33 | app = new Application("io.github.jwharm.javagi.examples.Browser", ApplicationFlags.DEFAULT_FLAGS); 34 | app.onActivate(this::activate); 35 | app.run(args); 36 | } 37 | 38 | public void activate() { 39 | // Webview component 40 | WebView webview = new WebView(); 41 | WebView.builder().build(); 42 | 43 | // Back button 44 | var back = Button.builder() 45 | .setIconName("go-previous-symbolic") 46 | .setTooltipText("Back") 47 | .build(); 48 | back.onClicked(webview::goBack); 49 | 50 | // Forward button 51 | var forward = Button.builder() 52 | .setIconName("go-next-symbolic") 53 | .setTooltipText("Forward") 54 | .build(); 55 | forward.onClicked(webview::goForward); 56 | 57 | // Stop / Reload button. 58 | var stopOrReload = Button.builder() 59 | .setIconName("process-stop-symbolic") 60 | .setTooltipText("Stop") 61 | .build(); 62 | stopOrReload.onClicked(() -> { 63 | if (loading) webview.stopLoading(); 64 | else webview.reload(); 65 | }); 66 | 67 | // Home button 68 | var home = Button.builder() 69 | .setIconName("go-home-symbolic") 70 | .setTooltipText("Home") 71 | .build(); 72 | home.onClicked(() -> webview.loadUri(HOME_PAGE)); 73 | 74 | // URL bar 75 | var urlBar = Entry.builder() 76 | .setInputPurpose(InputPurpose.URL) 77 | .setHexpand(true) 78 | .build(); 79 | 80 | // Container for the webview 81 | var viewContainer = Bin.builder() 82 | .setVexpand(true) 83 | .setHexpand(true) 84 | .build(); 85 | viewContainer.setChild(webview); 86 | 87 | // When navigating to another page, update the URL bar 88 | webview.bindProperty("uri", urlBar.getBuffer(), "text", BindingFlags.DEFAULT); 89 | 90 | // When the webview starts or finishes loading, switch the stop/reload icon and tooltip 91 | webview.onLoadChanged(event -> { 92 | switch (event) { 93 | case STARTED -> { 94 | loading = true; 95 | stopOrReload.setIconName("process-stop-symbolic"); 96 | stopOrReload.setTooltipText("Stop"); 97 | } 98 | case FINISHED -> { 99 | loading = false; 100 | stopOrReload.setIconName("view-refresh-symbolic"); 101 | stopOrReload.setTooltipText("Reload"); 102 | } 103 | default -> { 104 | // Ignore all other events 105 | } 106 | } 107 | }); 108 | 109 | // When the user navigates to a new URL 110 | urlBar.onActivate(() -> { 111 | var url = urlBar.getBuffer().getText(); 112 | var scheme = Uri.peekScheme(url); 113 | if (scheme == null) { 114 | url = "https://" + url; 115 | } 116 | webview.loadUri(url); 117 | }); 118 | 119 | // Update the progress indicator in the URL bar during loading 120 | webview.onNotify("estimated-load-progress", $ -> { 121 | urlBar.setProgressFraction(webview.getEstimatedLoadProgress()); 122 | if (urlBar.getProgressFraction() == 1) { 123 | GLib.timeoutAddOnce(500, () -> urlBar.setProgressFraction(0)); 124 | } 125 | }); 126 | 127 | // Start loading the home page 128 | webview.loadUri(HOME_PAGE); 129 | 130 | // Construct the header bar 131 | var headerbar = new HeaderBar(); 132 | headerbar.packStart(back); 133 | headerbar.packStart(forward); 134 | headerbar.packStart(stopOrReload); 135 | headerbar.packStart(home); 136 | headerbar.setTitleWidget(urlBar); 137 | 138 | // Pack everything together, and show the window 139 | var box = new Box(Orientation.VERTICAL, 0); 140 | box.append(headerbar); 141 | box.append(viewContainer); 142 | 143 | var window = ApplicationWindow.builder() 144 | .setApplication(app) 145 | .setHeightRequest(700) 146 | .setWidthRequest(700) 147 | .setContent(box) 148 | .build(); 149 | window.present(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Calculator/README.md: -------------------------------------------------------------------------------- 1 | ## Calculator 2 | 3 | This example is a simple calculator that uses Gtk and LibAdwaita. There's a header bar, and a grid-based layout for the buttons. The app reacts to key presses as expected. It loads a CSS file to apply a few small visual tweaks. 4 | 5 | To run the example, clone the repository, navigate to the `Calculator` folder, and execute `gradle run`. 6 | 7 | ![Calculator screenshot](calculator.png) 8 | -------------------------------------------------------------------------------- /Calculator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.21" 3 | application 4 | } 5 | 6 | group = "io.jwharm.javagi.examples" 7 | version = "1.0-SNAPSHOT" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation(kotlin("stdlib")) 15 | implementation("io.github.jwharm.javagi:gtk:0.12.2") 16 | implementation("io.github.jwharm.javagi:adw:0.12.2") 17 | } 18 | 19 | tasks.named("run") { 20 | jvmArgs("--enable-native-access=ALL-UNNAMED") 21 | } 22 | 23 | application { 24 | mainClass.set("io.jwharm.javagi.examples.MainKt") 25 | } 26 | 27 | kotlin { 28 | jvmToolchain(22) 29 | } 30 | -------------------------------------------------------------------------------- /Calculator/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/Calculator/calculator.png -------------------------------------------------------------------------------- /Calculator/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Calculator" 2 | -------------------------------------------------------------------------------- /Calculator/src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | package io.jwharm.javagi.examples 2 | 3 | import org.gnome.adw.Application 4 | import org.gnome.adw.ApplicationWindow 5 | import org.gnome.gdk.Display 6 | import org.gnome.gdk.Gdk 7 | import org.gnome.gtk.* 8 | import kotlin.system.exitProcess 9 | 10 | fun main(args: Array) { 11 | CalculatorApplication().run(args) 12 | } 13 | 14 | class CalculatorApplication : Application() { 15 | init { 16 | applicationId = "my.example.Calculator" 17 | 18 | onActivate { 19 | val provider = CssProvider() 20 | provider.loadFromPath("src/main/resources/Calculator.css") 21 | Gtk.styleContextAddProviderForDisplay( 22 | Display.getDefault(), 23 | provider, 24 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 25 | ) 26 | 27 | CalculatorWindow(this).present() 28 | } 29 | } 30 | } 31 | 32 | class CalculatorWindow(app: Application) : ApplicationWindow() { 33 | private var clean = false 34 | private var function: ((Double, Double) -> Double)? = null 35 | private var accumulator = 0.0 36 | private val buttonResult = createFunctionButton('=').apply { 37 | addCssClass("suggested-action") 38 | } 39 | 40 | private val entry: Entry = Entry().apply { 41 | placeholderText = "0.0" 42 | editable = false 43 | alignment = 1f // right-align 44 | addCssClass("monospace") 45 | } 46 | 47 | private val entryValue: Double 48 | get() = if (entry.text.isEmpty()) 0.0 else entry.text.toDouble() 49 | 50 | init { 51 | application = app 52 | title = "Calculator" 53 | setDefaultSize(250, 300) 54 | addController(EventControllerKey().apply { 55 | onKeyPressed { keyval, _, _ -> keyPressed(keyval) } 56 | }) 57 | 58 | content = Grid().apply { 59 | columnSpacing = 1 60 | rowSpacing = 1 61 | 62 | attach(HeaderBar().apply { 63 | hexpand = true 64 | packStart(Button.withLabel("AC").apply { 65 | addCssClass("destructive-action") 66 | onClicked(this@CalculatorWindow::clear) 67 | }) 68 | }, 0, 0, 4, 1) 69 | 70 | attach(entry, 0, 1, 4, 1) 71 | 72 | // Create the digit buttons and place them in the grid 73 | attach(createInputButton('7'), 0, 2, 1, 1) 74 | attach(createInputButton('8'), 1, 2, 1, 1) 75 | attach(createInputButton('9'), 2, 2, 1, 1) 76 | attach(createInputButton('4'), 0, 3, 1, 1) 77 | attach(createInputButton('5'), 1, 3, 1, 1) 78 | attach(createInputButton('6'), 2, 3, 1, 1) 79 | attach(createInputButton('1'), 0, 4, 1, 1) 80 | attach(createInputButton('2'), 1, 4, 1, 1) 81 | attach(createInputButton('3'), 2, 4, 1, 1) 82 | attach(createInputButton('0'), 0, 5, 1, 1) 83 | attach(createInputButton('.'), 1, 5, 1, 1) 84 | 85 | // Create the function buttons and place them in the grid 86 | attach(createFunctionButton('*'), 3, 2, 1, 1) 87 | attach(createFunctionButton('/'), 3, 3, 1, 1) 88 | attach(createFunctionButton('+'), 3, 4, 1, 1) 89 | attach(createFunctionButton('-'), 3, 5, 1, 1) 90 | attach(buttonResult.also { 91 | grabFocus() 92 | }, 2, 5, 1, 1) 93 | } 94 | } 95 | 96 | private fun createInputButton(label: Char): Button = Button.withLabel(label.toString()).apply { 97 | vexpand = true 98 | hexpand = true 99 | onClicked { 100 | input(label) 101 | } 102 | } 103 | 104 | private fun createFunctionButton(label: Char): Button = Button.withLabel(label.toString()).apply { 105 | vexpand = true 106 | hexpand = true 107 | onClicked { 108 | setFunction(label) 109 | } 110 | } 111 | 112 | private fun keyPressed(keyval: Int): Boolean { 113 | val key = Gdk.keyvalName(keyval) 114 | when(key) { 115 | "BackSpace" -> backspace() 116 | "equal" -> setFunction('=') 117 | "plus" -> setFunction('+') 118 | "minus" -> setFunction('-') 119 | "asterisk" -> setFunction('*') 120 | "slash" -> setFunction('/') 121 | "period", "comma" -> input('.') 122 | "Escape" -> exitProcess(0) 123 | "c" -> clear() 124 | in "0".."9" -> input(key[0]) 125 | } 126 | 127 | return true 128 | } 129 | 130 | private fun backspace() = with(entry.text) { 131 | if (this.isNotEmpty()) 132 | entry.text = this.substring(0, this.length - 1) 133 | } 134 | 135 | private fun input(input: Char) { 136 | if (clean) { 137 | accumulator = entryValue 138 | entry.text = "" 139 | clean = false 140 | } 141 | 142 | if (input.isDigit() || (input == '.' && !entry.text.contains('.'))) 143 | entry.text += input 144 | 145 | buttonResult.grabFocus() 146 | } 147 | 148 | private fun setFunction(input: Char) { 149 | calculate() 150 | buttonResult.grabFocus() 151 | function = when(input) { 152 | '+' -> { x, y -> x + y } 153 | '-' -> { x, y -> x - y } 154 | '*' -> { x, y -> x * y } 155 | '/' -> { x, y -> x / y } 156 | '=' -> null 157 | else -> function 158 | } 159 | } 160 | 161 | private fun calculate() { 162 | clean = true 163 | function?.let { 164 | entry.text = it(accumulator, entryValue).toString().replace("\\.0$", "") 165 | } 166 | } 167 | 168 | private fun clear() { 169 | entry.text = "" 170 | accumulator = 0.0 171 | clean = false 172 | function = null 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Calculator/src/main/resources/Calculator.css: -------------------------------------------------------------------------------- 1 | entry { 2 | color: var(--view-fg-color); 3 | background-color: var(--view-bg-color); 4 | } 5 | -------------------------------------------------------------------------------- /CodeEditor/README.md: -------------------------------------------------------------------------------- 1 | ## Source code editor 2 | 3 | This example is a small Adwaita application to edit text in a GtkSourceView component. 4 | 5 | To run the example, clone the repository, navigate to the `CodeEditor` folder, and execute `gradle run`. 6 | 7 | ![Code Editor screenshot](codeeditor.png) 8 | -------------------------------------------------------------------------------- /CodeEditor/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:adw:0.12.2' 11 | implementation 'io.github.jwharm.javagi:gtksourceview:0.12.2' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.codeeditor.CodeEditor" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /CodeEditor/codeeditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/CodeEditor/codeeditor.png -------------------------------------------------------------------------------- /CodeEditor/src/main/java/io/github/jwharm/javagi/examples/codeeditor/CodeEditor.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.codeeditor; 2 | 3 | import org.gnome.adw.Application; 4 | import org.gnome.gio.ApplicationFlags; 5 | 6 | public class CodeEditor extends Application { 7 | 8 | public static void main(String[] args) { 9 | var app = new CodeEditor(); 10 | app.run(args); 11 | } 12 | 13 | public CodeEditor () { 14 | setApplicationId("io.github.jwharm.javagi.examples.CodeEditor"); 15 | setFlags(ApplicationFlags.DEFAULT_FLAGS); 16 | } 17 | 18 | /** 19 | * When the application is activated, create a new EditorWindow 20 | */ 21 | @Override 22 | public void activate() { 23 | var win = this.getActiveWindow(); 24 | if (win == null) { 25 | win = new EditorWindow(this); 26 | } 27 | win.setDefaultSize(600, 400); 28 | win.present(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CodeEditor/src/main/java/io/github/jwharm/javagi/examples/codeeditor/EditorWindow.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.codeeditor; 2 | 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.base.GErrorException; 5 | import io.github.jwharm.javagi.base.Out; 6 | import org.gnome.adw.*; 7 | import org.gnome.adw.AlertDialog; 8 | import org.gnome.adw.Application; 9 | import org.gnome.adw.ApplicationWindow; 10 | import org.gnome.adw.HeaderBar; 11 | import org.gnome.gio.File; 12 | import org.gnome.gio.FileCreateFlags; 13 | import org.gnome.gtk.*; 14 | import org.gnome.gtksourceview.LanguageManager; 15 | import org.gnome.gtksourceview.View; 16 | 17 | /** 18 | * The EditorWindow class contains a headerbar and the sourceview. The headerbar 19 | * contains buttons for new/open/save actions. 20 | */ 21 | public class EditorWindow extends ApplicationWindow { 22 | 23 | // The currently open file (or null when no file is open) 24 | private File file = null; 25 | 26 | // The sourceview component 27 | private View sourceview; 28 | 29 | // A banner to show error messages 30 | private Banner banner; 31 | 32 | // Constructor for a new EditorWindow 33 | public EditorWindow(Application application) { 34 | setApplication(application); 35 | present(); 36 | 37 | // Make sure the text field has the keyboard focus. 38 | sourceview.grabFocus(); 39 | } 40 | 41 | /** 42 | * The @InstanceInit method is called during construction of a new EditorWindow instance. 43 | * It creates the window layout (the headerbar, buttons, and sourceview). 44 | */ 45 | @InstanceInit 46 | public void init() { 47 | var box = new Box(Orientation.VERTICAL, 0); 48 | 49 | // Create the headerbar 50 | var header = new HeaderBar(); 51 | box.append(header); 52 | 53 | banner = Banner.builder() 54 | .setButtonLabel("Dismiss") 55 | .onButtonClicked(() -> banner.setRevealed(false)) 56 | .build(); 57 | box.append(banner); 58 | 59 | // Create the GtkSourceView, with some sensible features enabled. 60 | sourceview = View.builder() 61 | .setMonospace(true) 62 | .setShowLineNumbers(true) 63 | .setHighlightCurrentLine(true) 64 | .setAutoIndent(true) 65 | .build(); 66 | 67 | // The window title contains a "modified" indicator, that is 68 | // updated by the "on-modified-change" signal of the text buffer. 69 | sourceview.getBuffer().onModifiedChanged(this::updateWindowTitle); 70 | 71 | // The textView should be scrollable. 72 | var scrolledWindow = ScrolledWindow.builder() 73 | .setChild(sourceview) 74 | .setVexpand(true) 75 | .build(); 76 | box.append(scrolledWindow); 77 | 78 | // Create buttons for 'new', 'open' and 'save' actions. 79 | var newButton = Button.fromIconName("document-new-symbolic"); 80 | newButton.onClicked(() -> whenSure(this::clear)); 81 | header.packStart(newButton); 82 | 83 | var openButton = Button.fromIconName("document-open-symbolic"); 84 | openButton.onClicked(() -> whenSure(this::open)); 85 | header.packStart(openButton); 86 | 87 | var saveButton = Button.fromIconName("document-save-symbolic"); 88 | saveButton.onClicked(this::save); 89 | header.packStart(saveButton); 90 | 91 | // Ask to save changes before closing the window. 92 | this.onCloseRequest(() -> { 93 | whenSure(this::destroy); 94 | return true; 95 | }); 96 | 97 | setContent(box); 98 | 99 | // Show the results. 100 | updateWindowTitle(); 101 | } 102 | 103 | /** 104 | * Updates the window title to a modified-indicator and the current filename. 105 | * When no file is open, the title is "Unnamed". 106 | */ 107 | private void updateWindowTitle() { 108 | super.setTitle( 109 | (sourceview.getBuffer().getModified() ? ("• ") : "") + 110 | (file == null ? "Unnamed" : file.getBasename()) 111 | ); 112 | } 113 | 114 | // Set source code language 115 | private void detectLanguage() { 116 | var buffer = (org.gnome.gtksourceview.Buffer) sourceview.getBuffer(); 117 | buffer.setLanguage(file == null ? null 118 | : LanguageManager.getDefault().guessLanguage(file.getBasename(), null)); 119 | } 120 | 121 | /** 122 | * Runs {@code action} but, if the buffer is modified, asks to save 123 | * the modifications first. 124 | * 125 | * @param action the action to run after saving the modifications 126 | */ 127 | private void whenSure(Runnable action) { 128 | // No modifications? 129 | if (! sourceview.getBuffer().getModified()) { 130 | action.run(); 131 | sourceview.grabFocus(); 132 | return; 133 | } 134 | 135 | var dialog = new AlertDialog("Save changes?", "Do you want to save your changes?"); 136 | dialog.addResponses("cancel", "Cancel", "discard", "Discard", "save", "Save", null); 137 | dialog.setResponseAppearance("save", ResponseAppearance.SUGGESTED); 138 | dialog.setResponseAppearance("discard", ResponseAppearance.DESTRUCTIVE); 139 | dialog.setDefaultResponse("cancel"); 140 | dialog.setCloseResponse("cancel"); 141 | dialog.onResponse(null, response -> { 142 | if (!response.equals("cancel")) { 143 | if (response.equals("save")) save(); 144 | action.run(); 145 | } 146 | sourceview.grabFocus(); 147 | }); 148 | dialog.present(this); 149 | } 150 | 151 | /** 152 | * "New" action: clear the editor buffer. 153 | * This could also open a new window, instead of cleaning the current buffer. 154 | */ 155 | public void clear() { 156 | file = null; 157 | sourceview.getBuffer().setText("", 0); 158 | detectLanguage(); 159 | sourceview.getBuffer().setModified(false); 160 | sourceview.grabFocus(); 161 | } 162 | 163 | /** 164 | * "Open" action: Load a file and show the contents in the editor. 165 | */ 166 | public void open() { 167 | // Set up an Open File dialog. 168 | var dialog = new FileDialog(); 169 | dialog.openMultiple(this, null, (_, result, _) -> { 170 | try { 171 | var files = dialog.openMultipleFinish(result); 172 | file = (File) files.getItem(0); 173 | } catch (GErrorException ignored) {} // used clicked cancel 174 | if (file == null) return; 175 | 176 | // Load the contents of the selected file. 177 | try { 178 | // The byte[] parameter is an out-parameter in the C API. 179 | // Create an empty Out object, and read its value afterward. 180 | Out contents = new Out<>(); 181 | file.loadContents(null, contents, null); 182 | sourceview.getBuffer().setText(new String(contents.get()), contents.get().length); 183 | detectLanguage(); 184 | sourceview.getBuffer().setModified(false); 185 | sourceview.grabFocus(); 186 | } catch (GErrorException e) { 187 | banner.setTitle("Error reading from file: " + e.getMessage()); 188 | banner.setRevealed(true); 189 | } 190 | }); 191 | } 192 | 193 | /** 194 | * "Save" action: Show a file dialog (for new files) and call {@link #write()} 195 | */ 196 | public void save() { 197 | if (file == null) { 198 | // Set up a Save File dialog. 199 | var dialog = new FileDialog(); 200 | dialog.save(this, null, (_, result, _) -> { 201 | try { 202 | file = dialog.saveFinish(result); 203 | if (file == null) return; 204 | 205 | // Write the sourceview buffer contents to the selected file. 206 | write(); 207 | detectLanguage(); 208 | sourceview.getBuffer().setModified(false); 209 | sourceview.grabFocus(); 210 | } catch (GErrorException ignored) {} // used clicked cancel 211 | }); 212 | } else { 213 | // Write the sourceview buffer contents to the file that was already open. 214 | write(); 215 | sourceview.getBuffer().setModified(false); 216 | sourceview.grabFocus(); 217 | } 218 | } 219 | 220 | /** 221 | * Helper function that writes editor contents to a file. 222 | */ 223 | private void write() { 224 | // Get the contents of the sourceview buffer as a byte array 225 | TextIter start = new TextIter(); 226 | TextIter end = new TextIter(); 227 | sourceview.getBuffer().getBounds(start, end); 228 | byte[] contents = sourceview.getBuffer().getText(start, end, false).getBytes(); 229 | try { 230 | // Write the byte array to the file 231 | file.replaceContents(contents, "", false, FileCreateFlags.NONE, null, null); 232 | } catch (GErrorException e) { 233 | banner.setTitle("Error reading from file: " + e.getMessage()); 234 | banner.setRevealed(true); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /ColumnViewDatagrid/README.md: -------------------------------------------------------------------------------- 1 | ## ColumnView Datagrid 2 | 3 | This example displays a long, scrollable datagrid with simple string data, demonstrating how to use a Gio ListStore with a custom Java class for the row data. It is originally based on [this Gtk-rs example](https://github.com/gtk-rs/gtk4-rs/tree/master/examples/column_view_datagrid). 4 | 5 | To run the example, clone the repository, navigate to the `ColumnViewDatagrid` folder, and execute `gradle run`. 6 | 7 | ![ColumnView Datagrid screenshot](columnview.png) 8 | -------------------------------------------------------------------------------- /ColumnViewDatagrid/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += '--enable-native-access=ALL-UNNAMED' 21 | } 22 | 23 | application { 24 | mainClass = 'io.github.jwharm.javagi.examples.columnview.ColumnViewDatagrid' 25 | } 26 | -------------------------------------------------------------------------------- /ColumnViewDatagrid/columnview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/ColumnViewDatagrid/columnview.png -------------------------------------------------------------------------------- /ColumnViewDatagrid/src/main/java/io/github/jwharm/javagi/examples/columnview/ColumnViewDatagrid.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.columnview; 2 | 3 | import io.github.jwharm.javagi.gobject.types.Types; 4 | import org.gnome.gio.ApplicationFlags; 5 | import org.gnome.glib.Type; 6 | import org.gnome.gobject.GObject; 7 | import org.gnome.gtk.*; 8 | import org.gnome.gio.ListStore; 9 | 10 | /** 11 | * Example class for constructing a Gtk ColumnView with a lengthy table of data. 12 | */ 13 | public class ColumnViewDatagrid { 14 | 15 | Application app; 16 | ListStore store; 17 | 18 | public ColumnViewDatagrid(String[] args) { 19 | app = new Application("my.example.ColumnView", ApplicationFlags.DEFAULT_FLAGS); 20 | app.onActivate(this::onActivate); 21 | app.run(args); 22 | } 23 | 24 | private void onActivate() { 25 | /* 26 | * The ListStore is the data model behind our Gtk ColumnView. It is 27 | * populated with Row instances. The Row class is defined below. For 28 | * this example it simply contains two Strings, one for each column. 29 | */ 30 | store = new ListStore<>(Row.gtype); 31 | for (int i = 0; i < 1000; i++) 32 | store.append(new Row("col1 " + i, "col2 " + i)); 33 | 34 | // Create the ColumnView and put it in a scrollable window 35 | var sel = new SingleSelection(store); 36 | var columnview = createColumnView(sel); 37 | var scroll = ScrolledWindow.builder() 38 | // Disable horizontal scrolling 39 | .setHscrollbarPolicy(PolicyType.NEVER) 40 | .build(); 41 | scroll.setChild(columnview); 42 | 43 | // Create and present the window 44 | var window = ApplicationWindow.builder() 45 | .setDefaultWidth(320) 46 | .setDefaultHeight(480) 47 | .setApplication(app) 48 | .setTitle("ColumnView Example") 49 | .build(); 50 | window.setChild(scroll); 51 | window.present(); 52 | } 53 | 54 | private static ColumnView createColumnView(SingleSelection sel) { 55 | var columnview = new ColumnView(sel); 56 | 57 | // One ListItemFactory for each column 58 | var col1factory = new SignalListItemFactory(); 59 | var col2factory = new SignalListItemFactory(); 60 | 61 | /* 62 | * A SignalListItemFactory creates and binds widgets for the list items. 63 | * We use a Gtk Inscription widget to display the text. 64 | */ 65 | col1factory.onSetup(item -> { 66 | var listitem = (ListItem) item; 67 | var inscription = Inscription.builder() 68 | .setXalign(0) 69 | .build(); 70 | listitem.setChild(inscription); 71 | }); 72 | col1factory.onBind(item -> { 73 | var listitem = (ListItem) item; 74 | var inscription = (Inscription) listitem.getChild(); 75 | var row = (Row) listitem.getItem(); 76 | inscription.setText(row.col1); 77 | }); 78 | 79 | col2factory.onSetup(item -> { 80 | var listitem = (ListItem) item; 81 | var inscription = Inscription.builder() 82 | .setXalign(0) 83 | .build(); 84 | listitem.setChild(inscription); 85 | }); 86 | col2factory.onBind(item -> { 87 | var listitem = (ListItem) item; 88 | var inscription = (Inscription) listitem.getChild(); 89 | var row = (Row) listitem.getItem(); 90 | inscription.setText(row.col2); 91 | }); 92 | 93 | // Create the columns and add them to the column view 94 | var col1 = new ColumnViewColumn("Column 1", col1factory); 95 | var col2 = new ColumnViewColumn("Column 2", col2factory); 96 | columnview.appendColumn(col1); 97 | columnview.appendColumn(col2); 98 | return columnview; 99 | } 100 | 101 | /** 102 | * This class represents one row of data. To use it in a ListStore, it must 103 | * be registered as a GObject-derived type. Other than that, it's just a 104 | * plain Java class. 105 | */ 106 | public static final class Row extends GObject { 107 | public static Type gtype = Types.register(Row.class); 108 | public String col1; 109 | public String col2; 110 | 111 | public Row(String col1, String col2) { 112 | this.col1 = col1; 113 | this.col2 = col2; 114 | } 115 | } 116 | 117 | public static void main(String[] args) { 118 | new ColumnViewDatagrid(args); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /GettingStarted/README.md: -------------------------------------------------------------------------------- 1 | ## Example programs for the "Getting started" guide 2 | 3 | These programs are the full source code accompanying the "Getting started" guide available [here](https://jwharm.github.io/java-gi/getting-started/getting_started_00/) 4 | 5 | Each example can be built and run separately by executing `gradle run` from the command line. 6 | -------------------------------------------------------------------------------- /GettingStarted/example-0/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "Example0" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-0/src/main/java/Example0.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gtk.*; 2 | import org.gnome.gio.ApplicationFlags; 3 | 4 | public class Example0 { 5 | 6 | public static void main(String[] args) { 7 | Application app = new Application("org.gtk.example", ApplicationFlags.DEFAULT_FLAGS); 8 | app.onActivate(() -> { 9 | Window window = new ApplicationWindow(app); 10 | window.setTitle("Window"); 11 | window.setDefaultSize(200, 200); 12 | window.present(); 13 | }); 14 | app.run(args); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /GettingStarted/example-1/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.4' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "Example1" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-1/src/main/java/Example1.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gtk.*; 2 | import org.gnome.gio.ApplicationFlags; 3 | 4 | public class Example1 { 5 | 6 | private static void printHello() { 7 | System.out.println("Hello World"); 8 | } 9 | 10 | private static void activate(Application app) { 11 | Window window = new ApplicationWindow(app); 12 | window.setTitle("Window"); 13 | window.setDefaultSize(200, 200); 14 | 15 | Box box = new Box(Orientation.VERTICAL, 0); 16 | box.setHalign(Align.CENTER); 17 | box.setValign(Align.CENTER); 18 | 19 | window.setChild(box); 20 | 21 | Button button = Button.withLabel("Hello World"); 22 | 23 | button.onClicked(Example1::printHello); 24 | button.onClicked(window::destroy); 25 | 26 | box.append(button); 27 | 28 | window.present(); 29 | } 30 | 31 | public static void main(String[] args) { 32 | Application app = new Application("org.gtk.example", ApplicationFlags.DEFAULT_FLAGS); 33 | app.onActivate(() -> activate(app)); 34 | app.run(args); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /GettingStarted/example-2/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "Example2" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-2/src/main/java/Example2.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gtk.*; 2 | import org.gnome.gio.ApplicationFlags; 3 | 4 | public class Example2 { 5 | 6 | private static void printHello() { 7 | System.out.println("Hello World"); 8 | } 9 | 10 | private static void activate(Application app) { 11 | // create a new window, and set its title 12 | Window window = new ApplicationWindow(app); 13 | window.setTitle("Window"); 14 | 15 | // Here we construct the container that is going pack our buttons 16 | Grid grid = new Grid(); 17 | 18 | // Pack the container in the window 19 | window.setChild(grid); 20 | 21 | Button button = Button.withLabel("Button 1"); 22 | button.onClicked(Example2::printHello); 23 | 24 | // Place the first button in the grid cell (0, 0), and make it fill 25 | // just 1 cell horizontally and vertically (ie no spanning) 26 | grid.attach(button, 0, 0, 1, 1); 27 | 28 | button = Button.withLabel("Button 2"); 29 | button.onClicked(Example2::printHello); 30 | 31 | // Place the second button in the grid cell (1, 0), and make it fill 32 | // just 1 cell horizontally and vertically (ie no spanning) 33 | grid.attach(button, 1, 0, 1, 1); 34 | 35 | button = Button.withLabel("Quit"); 36 | button.onClicked(window::destroy); 37 | 38 | // Place the Quit button in the grid cell (0, 1), and make it 39 | // span 2 columns. 40 | grid.attach(button, 0, 1, 2, 1); 41 | 42 | window.present(); 43 | } 44 | 45 | public static void main(String[] args) { 46 | Application app = new Application("org.gtk.example", ApplicationFlags.DEFAULT_FLAGS); 47 | app.onActivate(() -> activate(app)); 48 | app.run(args); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /GettingStarted/example-3/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "Example3" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-3/src/main/java/Example3.java: -------------------------------------------------------------------------------- 1 | import org.freedesktop.cairo.*; 2 | import org.gnome.gtk.*; 3 | import org.gnome.gdk.Gdk; 4 | import org.gnome.gio.ApplicationFlags; 5 | import java.io.IOException; 6 | 7 | public class Example3 { 8 | 9 | private final Application app; 10 | 11 | // Surface to store current scribbles 12 | private Surface surface = null; 13 | private DrawingArea drawingArea; 14 | 15 | private double startX; 16 | private double startY; 17 | 18 | private void clearSurface() { 19 | try { 20 | Context cr = Context.create(this.surface); 21 | cr.setSourceRGB(1, 1, 1); 22 | cr.paint(); 23 | } catch (IOException ignored) {} 24 | } 25 | 26 | // Create a new surface of the appropriate size to store our scribbles 27 | private void resize(Widget widget) { 28 | if (widget.getNative().getSurface() != null) { 29 | this.surface = ImageSurface.create( 30 | Format.ARGB32, widget.getWidth(), widget.getHeight()); 31 | 32 | // Initialize the surface to white 33 | clearSurface(); 34 | } 35 | } 36 | 37 | /* 38 | * Redraw the screen from the surface. Note that the draw 39 | * callback receives a ready-to-be-used Context that is already 40 | * clipped to only draw the exposed areas of the widget 41 | */ 42 | private void redraw(DrawingArea area, Context cr, int width, int height) { 43 | cr.setSource(this.surface, 0, 0); 44 | cr.paint(); 45 | } 46 | 47 | // Draw a rectangle on the surface at the given position 48 | private void drawBrush(double x, double y) { 49 | try { 50 | // Paint to the surface, where we store our state 51 | Context cr = Context.create(this.surface); 52 | 53 | cr.rectangle(x - 3, y - 3, 6, 6); 54 | cr.fill(); 55 | } catch (IOException ignored) {} 56 | 57 | // Now invalidate the drawing area. 58 | drawingArea.queueDraw(); 59 | } 60 | 61 | private void dragBegin(double x, double y) { 62 | this.startX = x; 63 | this.startY = y; 64 | drawBrush(x, y); 65 | } 66 | 67 | private void dragUpdate(double x, double y) { 68 | drawBrush(startX + x, startY + y); 69 | } 70 | 71 | private void dragEnd(double x, double y) { 72 | drawBrush(startX + x, startY + y); 73 | } 74 | 75 | private void pressed() { 76 | clearSurface(); 77 | drawingArea.queueDraw(); 78 | } 79 | 80 | private void activate() { 81 | Window window = new ApplicationWindow(this.app); 82 | window.setTitle("Drawing Area"); 83 | 84 | Frame frame = new Frame((String) null); 85 | window.setChild(frame); 86 | 87 | drawingArea = new DrawingArea(); 88 | // set a minimum size 89 | drawingArea.setSizeRequest(100, 100); 90 | 91 | frame.setChild(drawingArea); 92 | 93 | drawingArea.setDrawFunc(this::redraw); 94 | 95 | // Connect to the "resize" signal with "after"=true 96 | var callback = (DrawingArea.ResizeCallback) (x, y) -> resize(drawingArea); 97 | drawingArea.connect("resize", callback, true); 98 | 99 | GestureDrag drag = new GestureDrag(); 100 | drag.setButton(Gdk.BUTTON_PRIMARY); 101 | drawingArea.addController(drag); 102 | drag.onDragBegin(this::dragBegin); 103 | drag.onDragUpdate(this::dragUpdate); 104 | drag.onDragEnd(this::dragEnd); 105 | 106 | GestureClick press = new GestureClick(); 107 | press.setButton(Gdk.BUTTON_SECONDARY); 108 | drawingArea.addController(press); 109 | 110 | press.onPressed((n, x, y) -> pressed()); 111 | 112 | window.present(); 113 | } 114 | 115 | public Example3(String[] args) { 116 | this.app = new Application("org.gtk.example", ApplicationFlags.DEFAULT_FLAGS); 117 | app.onActivate(this::activate); 118 | app.run(args); 119 | } 120 | 121 | public static void main(String[] args) { 122 | new Example3(args); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /GettingStarted/example-4/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "Example4" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-4/src/main/java/Example4.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.ApplicationFlags; 3 | import org.gnome.gtk.*; 4 | 5 | public class Example4 { 6 | 7 | private static void printHello() { 8 | System.out.println("Hello World"); 9 | } 10 | 11 | private static void activate(Application app) { 12 | // Construct a GtkBuilder instance and load our UI description 13 | GtkBuilder builder = new GtkBuilder(); 14 | try { 15 | builder.addFromFile("src/main/resources/builder.ui"); 16 | } catch (GErrorException ignored) {} 17 | 18 | // Connect signal handlers to the constructed widgets. 19 | Window window = (Window) builder.getObject("window"); 20 | window.setApplication(app); 21 | 22 | Button button = (Button) builder.getObject("button1"); 23 | button.onClicked(Example4::printHello); 24 | 25 | button = (Button) builder.getObject("button2"); 26 | button.onClicked(Example4::printHello); 27 | 28 | button = (Button) builder.getObject("quit"); 29 | button.onClicked(window::destroy); 30 | 31 | window.setVisible(true); 32 | } 33 | 34 | public static void main(String[] args) { 35 | Application app = new Application("org.gtk.example", ApplicationFlags.DEFAULT_FLAGS); 36 | app.onActivate(() -> activate(app)); 37 | app.run(args); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GettingStarted/example-4/src/main/resources/builder.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Grid 5 | 6 | 7 | 8 | 9 | Button 1 10 | 11 | 0 12 | 0 13 | 14 | 15 | 16 | 17 | 18 | Button 2 19 | 20 | 1 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | Quit 28 | 29 | 0 30 | 1 31 | 2 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part1/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "ExampleMainClass" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part1/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.glib.List; 4 | import org.gnome.gtk.Application; 5 | import org.gnome.gtk.Window; 6 | 7 | public class ExampleApp extends Application { 8 | 9 | @Override 10 | public void activate() { 11 | ExampleAppWindow win = new ExampleAppWindow(this); 12 | win.present(); 13 | } 14 | 15 | @Override 16 | public void open(File[] files, String hint) { 17 | ExampleAppWindow win; 18 | List windows = super.getWindows(); 19 | if (!windows.isEmpty()) 20 | win = (ExampleAppWindow) windows.getFirst(); 21 | else 22 | win = new ExampleAppWindow(this); 23 | 24 | for (File file : files) 25 | win.open(file); 26 | 27 | win.present(); 28 | } 29 | 30 | public ExampleApp() { 31 | setApplicationId("org.gtk.exampleapp"); 32 | setFlags(ApplicationFlags.HANDLES_OPEN); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part1/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.File; 2 | import org.gnome.gtk.ApplicationWindow; 3 | 4 | public class ExampleAppWindow extends ApplicationWindow { 5 | 6 | public ExampleAppWindow(ExampleApp app) { 7 | setApplication(app); 8 | } 9 | 10 | public void open(File file) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part1/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | public class ExampleMainClass { 2 | 3 | public static void main(String[] args) { 4 | new ExampleApp().run(args); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.register('compileResources') { 20 | exec { 21 | workingDir 'src/main/resources' 22 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 23 | } 24 | } 25 | 26 | tasks.named('classes') { 27 | dependsOn compileResources 28 | } 29 | 30 | tasks.named('run') { 31 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 32 | } 33 | 34 | application { 35 | mainClass = "ExampleMainClass" 36 | } 37 | 38 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.glib.List; 4 | import org.gnome.gtk.Application; 5 | import org.gnome.gtk.Window; 6 | 7 | public class ExampleApp extends Application { 8 | 9 | @Override 10 | public void activate() { 11 | ExampleAppWindow win = new ExampleAppWindow(this); 12 | win.present(); 13 | } 14 | 15 | @Override 16 | public void open(File[] files, String hint) { 17 | ExampleAppWindow win; 18 | List windows = super.getWindows(); 19 | if (!windows.isEmpty()) 20 | win = (ExampleAppWindow) windows.getFirst(); 21 | else 22 | win = new ExampleAppWindow(this); 23 | 24 | for (File file : files) 25 | win.open(file); 26 | 27 | win.present(); 28 | } 29 | 30 | public ExampleApp() { 31 | setApplicationId("org.gtk.exampleapp"); 32 | setFlags(ApplicationFlags.HANDLES_OPEN); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 2 | import org.gnome.gio.File; 3 | import org.gnome.gtk.ApplicationWindow; 4 | 5 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 6 | public class ExampleAppWindow extends ApplicationWindow { 7 | 8 | public ExampleAppWindow(ExampleApp app) { 9 | setApplication(app); 10 | } 11 | 12 | public void open(File file) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | 6 | 7 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part2/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ignored (program name)", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.glib.List; 4 | import org.gnome.gtk.Application; 5 | import org.gnome.gtk.Window; 6 | 7 | public class ExampleApp extends Application { 8 | 9 | @Override 10 | public void activate() { 11 | ExampleAppWindow win = new ExampleAppWindow(this); 12 | win.present(); 13 | } 14 | 15 | @Override 16 | public void open(File[] files, String hint) { 17 | ExampleAppWindow win; 18 | List windows = super.getWindows(); 19 | if (!windows.isEmpty()) 20 | win = (ExampleAppWindow) windows.getFirst(); 21 | else 22 | win = new ExampleAppWindow(this); 23 | 24 | for (File file : files) 25 | win.open(file); 26 | 27 | win.present(); 28 | } 29 | 30 | public ExampleApp() { 31 | setApplicationId("org.gtk.exampleapp"); 32 | setFlags(ApplicationFlags.HANDLES_OPEN); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 5 | import io.github.jwharm.javagi.gtk.types.TemplateTypes; 6 | import org.gnome.gio.File; 7 | import org.gnome.glib.Type; 8 | import org.gnome.gobject.GObject; 9 | import org.gnome.gtk.ApplicationWindow; 10 | import org.gnome.gtk.ScrolledWindow; 11 | import org.gnome.gtk.Stack; 12 | import org.gnome.gtk.TextView; 13 | 14 | import java.lang.foreign.MemorySegment; 15 | 16 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 17 | public class ExampleAppWindow extends ApplicationWindow { 18 | 19 | @GtkChild 20 | public Stack stack; 21 | 22 | public ExampleAppWindow(ExampleApp app) { 23 | setApplication(app); 24 | } 25 | 26 | public void open(File file) { 27 | String basename = file.getBasename(); 28 | 29 | var scrolled = new ScrolledWindow(); 30 | scrolled.setHexpand(true); 31 | scrolled.setVexpand(true); 32 | var view = new TextView(); 33 | view.setEditable(false); 34 | view.setCursorVisible(false); 35 | scrolled.setChild(view); 36 | stack.addTitled(scrolled, basename, basename); 37 | 38 | try { 39 | var contents = new Out(); 40 | if (file.loadContents(null, contents, null)) { 41 | var buffer = view.getBuffer(); 42 | String str = new String(contents.get()); 43 | buffer.setText(str, str.length()); 44 | } 45 | } catch (GErrorException e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | 6 | 7 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part3/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ignored (program name)", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.gio.SimpleAction; 4 | import org.gnome.glib.List; 5 | import org.gnome.glib.Variant; 6 | import org.gnome.gtk.Application; 7 | import org.gnome.gtk.Window; 8 | 9 | public class ExampleApp extends Application { 10 | 11 | @Override 12 | public void activate() { 13 | ExampleAppWindow win = new ExampleAppWindow(this); 14 | win.present(); 15 | } 16 | 17 | @Override 18 | public void open(File[] files, String hint) { 19 | ExampleAppWindow win; 20 | List windows = super.getWindows(); 21 | if (!windows.isEmpty()) 22 | win = (ExampleAppWindow) windows.getFirst(); 23 | else 24 | win = new ExampleAppWindow(this); 25 | 26 | for (File file : files) 27 | win.open(file); 28 | 29 | win.present(); 30 | } 31 | 32 | public void preferencesActivated(Variant parameter) { 33 | } 34 | 35 | public void quitActivated(Variant parameter) { 36 | super.quit(); 37 | } 38 | 39 | @Override 40 | public void startup() { 41 | super.startup(); 42 | 43 | var preferences = new SimpleAction("preferences", null); 44 | preferences.onActivate(this::preferencesActivated); 45 | addAction(preferences); 46 | 47 | var quit = new SimpleAction("quit", null); 48 | quit.onActivate(this::quitActivated); 49 | addAction(quit); 50 | 51 | String[] quitAccels = new String[]{"q"}; 52 | setAccelsForAction("app.quit", quitAccels); 53 | } 54 | 55 | public ExampleApp() { 56 | setApplicationId("org.gtk.exampleapp"); 57 | setFlags(ApplicationFlags.HANDLES_OPEN); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 5 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 6 | import org.gnome.gio.File; 7 | import org.gnome.gio.MenuModel; 8 | import org.gnome.gtk.*; 9 | 10 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 11 | public class ExampleAppWindow extends ApplicationWindow { 12 | 13 | @GtkChild 14 | public Stack stack; 15 | 16 | @GtkChild 17 | public MenuButton gears; 18 | 19 | public ExampleAppWindow(ExampleApp app) { 20 | setApplication(app); 21 | } 22 | 23 | @InstanceInit 24 | public void init() { 25 | var builder = GtkBuilder.fromResource("/org/gtk/exampleapp/gears-menu.ui"); 26 | var menu = (MenuModel) builder.getObject("menu"); 27 | gears.setMenuModel(menu); 28 | } 29 | 30 | public void open(File file) { 31 | String basename = file.getBasename(); 32 | 33 | var scrolled = new ScrolledWindow(); 34 | scrolled.setHexpand(true); 35 | scrolled.setVexpand(true); 36 | var view = new TextView(); 37 | view.setEditable(false); 38 | view.setCursorVisible(false); 39 | scrolled.setChild(view); 40 | stack.addTitled(scrolled, basename, basename); 41 | 42 | try { 43 | var contents = new Out(); 44 | if (file.loadContents(null, contents, null)) { 45 | var buffer = view.getBuffer(); 46 | String str = new String(contents.get()); 47 | buffer.setText(str, str.length()); 48 | } 49 | } catch (GErrorException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gears-menu.ui 6 | 7 | 8 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/resources/gears-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Preferences 7 | app.preferences 8 | 9 |
10 |
11 | 12 | _Quit 13 | app.quit 14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part4/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ignored (program name)", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gobject.types.Types; 2 | import org.gnome.gio.ApplicationFlags; 3 | import org.gnome.gio.File; 4 | import org.gnome.gio.SimpleAction; 5 | import org.gnome.glib.List; 6 | import org.gnome.glib.Type; 7 | import org.gnome.glib.Variant; 8 | import org.gnome.gobject.GObject; 9 | import org.gnome.gtk.Application; 10 | import org.gnome.gtk.Window; 11 | import java.lang.foreign.MemorySegment; 12 | 13 | public class ExampleApp extends Application { 14 | 15 | @Override 16 | public void activate() { 17 | ExampleAppWindow win = new ExampleAppWindow(this); 18 | win.present(); 19 | } 20 | 21 | @Override 22 | public void open(File[] files, String hint) { 23 | ExampleAppWindow win; 24 | List windows = super.getWindows(); 25 | if (!windows.isEmpty()) 26 | win = (ExampleAppWindow) windows.getFirst(); 27 | else 28 | win = new ExampleAppWindow(this); 29 | 30 | for (File file : files) 31 | win.open(file); 32 | 33 | win.present(); 34 | } 35 | 36 | public void preferencesActivated(Variant parameter) { 37 | ExampleAppWindow win = (ExampleAppWindow) getActiveWindow(); 38 | ExampleAppPrefs prefs = new ExampleAppPrefs(win); 39 | prefs.present(); 40 | } 41 | 42 | public void quitActivated(Variant parameter) { 43 | super.quit(); 44 | } 45 | 46 | @Override 47 | public void startup() { 48 | super.startup(); 49 | 50 | var preferences = new SimpleAction("preferences", null); 51 | preferences.onActivate(this::preferencesActivated); 52 | addAction(preferences); 53 | 54 | var quit = new SimpleAction("quit", null); 55 | quit.onActivate(this::quitActivated); 56 | addAction(quit); 57 | 58 | String[] quitAccels = new String[]{"q"}; 59 | setAccelsForAction("app.quit", quitAccels); 60 | } 61 | 62 | public ExampleApp() { 63 | setApplicationId("org.gtk.exampleapp"); 64 | setFlags(ApplicationFlags.HANDLES_OPEN); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/java/ExampleAppPrefs.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 2 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 3 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 4 | import org.gnome.gio.Settings; 5 | import org.gnome.gio.SettingsBindFlags; 6 | import org.gnome.gtk.ComboBoxText; 7 | import org.gnome.gtk.Dialog; 8 | import org.gnome.gtk.FontButton; 9 | 10 | @GtkTemplate(ui="/org/gtk/exampleapp/prefs.ui") 11 | public class ExampleAppPrefs extends Dialog { 12 | 13 | @GtkChild 14 | public FontButton font; 15 | 16 | @GtkChild 17 | public ComboBoxText transition; 18 | 19 | Settings settings; 20 | 21 | @InstanceInit 22 | public void init() { 23 | settings = new Settings("org.gtk.exampleapp"); 24 | settings.bind("font", font, "font", SettingsBindFlags.DEFAULT); 25 | settings.bind("transition", transition, "active-id", SettingsBindFlags.DEFAULT); 26 | } 27 | 28 | public ExampleAppPrefs(ExampleAppWindow win) { 29 | setTransientFor(win); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 5 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 6 | import org.gnome.gio.File; 7 | import org.gnome.gio.MenuModel; 8 | import org.gnome.gio.SettingsBindFlags; 9 | import org.gnome.gtk.*; 10 | import org.gnome.gio.Settings; 11 | 12 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 13 | public class ExampleAppWindow extends ApplicationWindow { 14 | 15 | @GtkChild 16 | public Stack stack; 17 | 18 | @GtkChild 19 | public MenuButton gears; 20 | 21 | public Settings settings; 22 | 23 | public ExampleAppWindow(ExampleApp app) { 24 | setApplication(app); 25 | } 26 | 27 | @InstanceInit 28 | public void init() { 29 | var builder = GtkBuilder.fromResource("/org/gtk/exampleapp/gears-menu.ui"); 30 | var menu = (MenuModel) builder.getObject("menu"); 31 | gears.setMenuModel(menu); 32 | 33 | settings = new Settings("org.gtk.exampleapp"); 34 | settings.bind("transition", stack, "transition-type", SettingsBindFlags.DEFAULT); 35 | } 36 | 37 | public void open(File file) { 38 | String basename = file.getBasename(); 39 | 40 | var scrolled = new ScrolledWindow(); 41 | scrolled.setHexpand(true); 42 | scrolled.setVexpand(true); 43 | var view = new TextView(); 44 | view.setEditable(false); 45 | view.setCursorVisible(false); 46 | scrolled.setChild(view); 47 | stack.addTitled(scrolled, basename, basename); 48 | var buffer = view.getBuffer(); 49 | 50 | try { 51 | var contents = new Out(); 52 | if (file.loadContents(null, contents, null)) { 53 | String str = new String(contents.get()); 54 | buffer.setText(str, str.length()); 55 | } 56 | } catch (GErrorException e) { 57 | throw new RuntimeException(e); 58 | } 59 | 60 | var tag = buffer.createTag(null, null); 61 | settings.bind("font", tag, "font", SettingsBindFlags.DEFAULT); 62 | TextIter startIter = new TextIter(); 63 | TextIter endIter = new TextIter(); 64 | buffer.getStartIter(startIter); 65 | buffer.getEndIter(endIter); 66 | buffer.applyTag(tag, startIter, endIter); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gears-menu.ui 6 | prefs.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/resources/gears-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Preferences 7 | app.preferences 8 | 9 |
10 |
11 | 12 | _Quit 13 | app.quit 14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/resources/org.gtk.exampleapp.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 'Monospace 12' 6 | Font 7 | The font to be used for content. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'none' 16 | Transition 17 | The transition to use when switching tabs. 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/resources/prefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part6/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ExampleApp", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.gio.SimpleAction; 4 | import org.gnome.glib.List; 5 | import org.gnome.glib.Variant; 6 | import org.gnome.gtk.Application; 7 | import org.gnome.gtk.Window; 8 | 9 | public class ExampleApp extends Application { 10 | 11 | @Override 12 | public void activate() { 13 | ExampleAppWindow win = new ExampleAppWindow(this); 14 | win.present(); 15 | } 16 | 17 | @Override 18 | public void open(File[] files, String hint) { 19 | ExampleAppWindow win; 20 | List windows = super.getWindows(); 21 | if (!windows.isEmpty()) 22 | win = (ExampleAppWindow) windows.getFirst(); 23 | else 24 | win = new ExampleAppWindow(this); 25 | 26 | for (File file : files) 27 | win.open(file); 28 | 29 | win.present(); 30 | } 31 | 32 | public void preferencesActivated(Variant parameter) { 33 | ExampleAppWindow win = (ExampleAppWindow) getActiveWindow(); 34 | ExampleAppPrefs prefs = new ExampleAppPrefs(win); 35 | prefs.present(); 36 | } 37 | 38 | public void quitActivated(Variant parameter) { 39 | super.quit(); 40 | } 41 | 42 | @Override 43 | public void startup() { 44 | super.startup(); 45 | 46 | var preferences = new SimpleAction("preferences", null); 47 | preferences.onActivate(this::preferencesActivated); 48 | addAction(preferences); 49 | 50 | var quit = new SimpleAction("quit", null); 51 | quit.onActivate(this::quitActivated); 52 | addAction(quit); 53 | 54 | String[] quitAccels = new String[]{"q"}; 55 | setAccelsForAction("app.quit", quitAccels); 56 | } 57 | 58 | public ExampleApp() { 59 | setApplicationId("org.gtk.exampleapp"); 60 | setFlags(ApplicationFlags.HANDLES_OPEN); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/java/ExampleAppPrefs.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 2 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 3 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 4 | import org.gnome.gio.Settings; 5 | import org.gnome.gio.SettingsBindFlags; 6 | import org.gnome.gtk.ComboBoxText; 7 | import org.gnome.gtk.Dialog; 8 | import org.gnome.gtk.FontButton; 9 | 10 | @GtkTemplate(ui="/org/gtk/exampleapp/prefs.ui") 11 | public class ExampleAppPrefs extends Dialog { 12 | 13 | @GtkChild 14 | public FontButton font; 15 | 16 | @GtkChild 17 | public ComboBoxText transition; 18 | 19 | Settings settings; 20 | 21 | @InstanceInit 22 | public void init() { 23 | settings = new Settings("org.gtk.exampleapp"); 24 | settings.bind("font", font, "font", SettingsBindFlags.DEFAULT); 25 | settings.bind("transition", transition, "active-id", SettingsBindFlags.DEFAULT); 26 | } 27 | 28 | public ExampleAppPrefs(ExampleAppWindow win) { 29 | setTransientFor(win); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkCallback; 5 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 6 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 7 | import org.gnome.gio.File; 8 | import org.gnome.gio.MenuModel; 9 | import org.gnome.gio.SettingsBindFlags; 10 | import org.gnome.gobject.BindingFlags; 11 | import org.gnome.gtk.*; 12 | import org.gnome.gio.Settings; 13 | 14 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 15 | public class ExampleAppWindow extends ApplicationWindow { 16 | 17 | @GtkChild public Stack stack; 18 | @GtkChild public MenuButton gears; 19 | @GtkChild public ToggleButton search; 20 | @GtkChild public SearchBar searchbar; 21 | @GtkChild public SearchEntry searchentry; 22 | 23 | public Settings settings; 24 | 25 | public ExampleAppWindow(ExampleApp app) { 26 | setApplication(app); 27 | } 28 | 29 | @InstanceInit 30 | public void init() { 31 | var builder = GtkBuilder.fromResource("/org/gtk/exampleapp/gears-menu.ui"); 32 | var menu = (MenuModel) builder.getObject("menu"); 33 | gears.setMenuModel(menu); 34 | 35 | settings = new Settings("org.gtk.exampleapp"); 36 | settings.bind("transition", stack, "transition-type", SettingsBindFlags.DEFAULT); 37 | search.bindProperty("active", searchbar, "search-mode-enabled", BindingFlags.BIDIRECTIONAL); 38 | } 39 | 40 | public void open(File file) { 41 | String basename = file.getBasename(); 42 | 43 | var scrolled = new ScrolledWindow(); 44 | scrolled.setHexpand(true); 45 | scrolled.setVexpand(true); 46 | var view = new TextView(); 47 | view.setEditable(false); 48 | view.setCursorVisible(false); 49 | scrolled.setChild(view); 50 | stack.addTitled(scrolled, basename, basename); 51 | var buffer = view.getBuffer(); 52 | 53 | try { 54 | var contents = new Out(); 55 | if (file.loadContents(null, contents, null)) { 56 | String str = new String(contents.get()); 57 | buffer.setText(str, str.length()); 58 | } 59 | } catch (GErrorException e) { 60 | throw new RuntimeException(e); 61 | } 62 | 63 | var tag = buffer.createTag(null, null); 64 | settings.bind("font", tag, "font", SettingsBindFlags.DEFAULT); 65 | TextIter startIter = new TextIter(); 66 | TextIter endIter = new TextIter(); 67 | buffer.getStartIter(startIter); 68 | buffer.getEndIter(endIter); 69 | buffer.applyTag(tag, startIter, endIter); 70 | 71 | search.setSensitive(true); 72 | } 73 | 74 | @GtkCallback(name="search_text_changed") 75 | public void searchTextChanged() { 76 | String text = searchentry.getText(); 77 | 78 | if (text.isEmpty()) 79 | return; 80 | 81 | var tab = (ScrolledWindow) stack.getVisibleChild(); 82 | var view = (TextView) tab.getChild(); 83 | var buffer = view.getBuffer(); 84 | 85 | // Very simple-minded search implementation 86 | TextIter startIter = new TextIter(); 87 | TextIter matchStart = new TextIter(); 88 | TextIter matchEnd = new TextIter(); 89 | buffer.getStartIter(startIter); 90 | if (startIter.forwardSearch(text, TextSearchFlags.CASE_INSENSITIVE, 91 | matchStart, matchEnd, null)) { 92 | buffer.selectRange(matchStart, matchEnd); 93 | view.scrollToIter(matchStart, 0.0, false, 0.0, 0.0); 94 | } 95 | } 96 | 97 | @GtkCallback(name="visible_child_changed") 98 | public void visibleChildChanged() { 99 | if (stack.inDestruction()) 100 | return; 101 | 102 | searchbar.setSearchMode(false); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gears-menu.ui 6 | prefs.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/resources/gears-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Preferences 7 | app.preferences 8 | 9 |
10 |
11 | 12 | _Quit 13 | app.quit 14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/resources/org.gtk.exampleapp.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 'Monospace 12' 6 | Font 7 | The font to be used for content. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'none' 16 | Transition 17 | The transition to use when switching tabs. 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/resources/prefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part7/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 47 | 48 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ExampleApp", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.gio.SimpleAction; 4 | import org.gnome.glib.List; 5 | import org.gnome.glib.Variant; 6 | import org.gnome.gtk.Application; 7 | import org.gnome.gtk.Window; 8 | 9 | public class ExampleApp extends Application { 10 | 11 | @Override 12 | public void activate() { 13 | ExampleAppWindow win = new ExampleAppWindow(this); 14 | win.present(); 15 | } 16 | 17 | @Override 18 | public void open(File[] files, String hint) { 19 | ExampleAppWindow win; 20 | List windows = super.getWindows(); 21 | if (!windows.isEmpty()) 22 | win = (ExampleAppWindow) windows.getFirst(); 23 | else 24 | win = new ExampleAppWindow(this); 25 | 26 | for (File file : files) 27 | win.open(file); 28 | 29 | win.present(); 30 | } 31 | 32 | public void preferencesActivated(Variant parameter) { 33 | ExampleAppWindow win = (ExampleAppWindow) getActiveWindow(); 34 | ExampleAppPrefs prefs = new ExampleAppPrefs(win); 35 | prefs.present(); 36 | } 37 | 38 | public void quitActivated(Variant parameter) { 39 | super.quit(); 40 | } 41 | 42 | @Override 43 | public void startup() { 44 | super.startup(); 45 | 46 | var preferences = new SimpleAction("preferences", null); 47 | preferences.onActivate(this::preferencesActivated); 48 | addAction(preferences); 49 | 50 | var quit = new SimpleAction("quit", null); 51 | quit.onActivate(this::quitActivated); 52 | addAction(quit); 53 | 54 | String[] quitAccels = new String[]{"q"}; 55 | setAccelsForAction("app.quit", quitAccels); 56 | } 57 | 58 | public ExampleApp() { 59 | setApplicationId("org.gtk.exampleapp"); 60 | setFlags(ApplicationFlags.HANDLES_OPEN); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/java/ExampleAppPrefs.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 2 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 3 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 4 | import org.gnome.gio.Settings; 5 | import org.gnome.gio.SettingsBindFlags; 6 | import org.gnome.gtk.ComboBoxText; 7 | import org.gnome.gtk.Dialog; 8 | import org.gnome.gtk.FontButton; 9 | 10 | @GtkTemplate(ui="/org/gtk/exampleapp/prefs.ui") 11 | public class ExampleAppPrefs extends Dialog { 12 | 13 | @GtkChild 14 | public FontButton font; 15 | 16 | @GtkChild 17 | public ComboBoxText transition; 18 | 19 | Settings settings; 20 | 21 | @InstanceInit 22 | public void init() { 23 | settings = new Settings("org.gtk.exampleapp"); 24 | settings.bind("font", font, "font", SettingsBindFlags.DEFAULT); 25 | settings.bind("transition", transition, "active-id", SettingsBindFlags.DEFAULT); 26 | } 27 | 28 | public ExampleAppPrefs(ExampleAppWindow win) { 29 | setTransientFor(win); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkCallback; 5 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 6 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 7 | import org.gnome.gio.File; 8 | import org.gnome.gio.MenuModel; 9 | import org.gnome.gio.SettingsBindFlags; 10 | import org.gnome.gobject.BindingFlags; 11 | import org.gnome.gtk.*; 12 | import org.gnome.gio.Settings; 13 | 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | 17 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 18 | public class ExampleAppWindow extends ApplicationWindow { 19 | 20 | @GtkChild public Stack stack; 21 | @GtkChild public MenuButton gears; 22 | @GtkChild public ToggleButton search; 23 | @GtkChild public SearchBar searchbar; 24 | @GtkChild public SearchEntry searchentry; 25 | @GtkChild public Revealer sidebar; 26 | @GtkChild public ListBox words; 27 | 28 | private Settings settings; 29 | 30 | public ExampleAppWindow(ExampleApp app) { 31 | setApplication(app); 32 | } 33 | 34 | @InstanceInit 35 | public void init() { 36 | var builder = GtkBuilder.fromResource("/org/gtk/exampleapp/gears-menu.ui"); 37 | var menu = (MenuModel) builder.getObject("menu"); 38 | gears.setMenuModel(menu); 39 | 40 | settings = new Settings("org.gtk.exampleapp"); 41 | settings.bind("transition", stack, "transition-type", SettingsBindFlags.DEFAULT); 42 | search.bindProperty("active", searchbar, "search-mode-enabled", BindingFlags.BIDIRECTIONAL); 43 | 44 | settings.bind("show-words", sidebar, "reveal-child", SettingsBindFlags.DEFAULT); 45 | sidebar.onNotify("reveal-child", _ -> updateWords()); 46 | addAction(settings.createAction("show-words")); 47 | } 48 | 49 | public void open(File file) { 50 | String basename = file.getBasename(); 51 | 52 | var scrolled = new ScrolledWindow(); 53 | scrolled.setHexpand(true); 54 | scrolled.setVexpand(true); 55 | var view = new TextView(); 56 | view.setEditable(false); 57 | view.setCursorVisible(false); 58 | scrolled.setChild(view); 59 | stack.addTitled(scrolled, basename, basename); 60 | var buffer = view.getBuffer(); 61 | 62 | try { 63 | var contents = new Out(); 64 | if (file.loadContents(null, contents, null)) { 65 | String str = new String(contents.get()); 66 | buffer.setText(str, str.length()); 67 | } 68 | } catch (GErrorException e) { 69 | throw new RuntimeException(e); 70 | } 71 | 72 | var tag = buffer.createTag(null, null); 73 | settings.bind("font", tag, "font", SettingsBindFlags.DEFAULT); 74 | TextIter startIter = new TextIter(); 75 | TextIter endIter = new TextIter(); 76 | buffer.getStartIter(startIter); 77 | buffer.getEndIter(endIter); 78 | buffer.applyTag(tag, startIter, endIter); 79 | 80 | search.setSensitive(true); 81 | 82 | updateWords(); 83 | } 84 | 85 | @GtkCallback(name="search_text_changed") 86 | public void searchTextChanged() { 87 | String text = searchentry.getText(); 88 | 89 | if (text.isEmpty()) 90 | return; 91 | 92 | var tab = (ScrolledWindow) stack.getVisibleChild(); 93 | var view = (TextView) tab.getChild(); 94 | var buffer = view.getBuffer(); 95 | 96 | // Very simple-minded search implementation 97 | TextIter startIter = new TextIter(); 98 | TextIter matchStart = new TextIter(); 99 | TextIter matchEnd = new TextIter(); 100 | buffer.getStartIter(startIter); 101 | if (startIter.forwardSearch(text, TextSearchFlags.CASE_INSENSITIVE, 102 | matchStart, matchEnd, null)) { 103 | buffer.selectRange(matchStart, matchEnd); 104 | view.scrollToIter(matchStart, 0.0, false, 0.0, 0.0); 105 | } 106 | } 107 | 108 | @GtkCallback(name="visible_child_changed") 109 | public void visibleChildChanged() { 110 | if (stack.inDestruction()) 111 | return; 112 | 113 | searchbar.setSearchMode(false); 114 | updateWords(); 115 | } 116 | 117 | private void findWord(Button button) { 118 | String word = button.getLabel(); 119 | searchentry.setText(word); 120 | } 121 | 122 | private void updateWords() { 123 | var tab = (ScrolledWindow) stack.getVisibleChild(); 124 | if (tab == null) 125 | return; 126 | 127 | var view = (TextView) tab.getChild(); 128 | var buffer = view.getBuffer(); 129 | 130 | Set strings = new HashSet<>(); 131 | 132 | TextIter end, start = new TextIter(); 133 | buffer.getStartIter(start); 134 | 135 | outer: 136 | while (!start.isEnd()) { 137 | while (!start.startsWord()) 138 | if (!start.forwardChar()) 139 | break outer; 140 | 141 | end = start.copy(); 142 | if (!end.forwardWordEnd()) 143 | break; 144 | 145 | var word = buffer.getText(start, end, false); 146 | strings.add(word.toLowerCase()); 147 | start = end.copy(); 148 | } 149 | 150 | Widget child; 151 | while ((child = words.getFirstChild()) != null) 152 | words.remove(child); 153 | 154 | for (var key : strings) { 155 | var row = Button.withLabel(key); 156 | row.onClicked(() -> findWord(row)); 157 | words.insert(row, -1); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gears-menu.ui 6 | prefs.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/resources/gears-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Words 7 | win.show-words 8 | 9 | 10 | _Preferences 11 | app.preferences 12 | 13 |
14 |
15 | 16 | _Quit 17 | app.quit 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/resources/org.gtk.exampleapp.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 'Monospace 12' 6 | Font 7 | The font to be used for content. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'none' 16 | Transition 17 | The transition to use when switching tabs. 18 | 19 | 20 | false 21 | Show words 22 | Whether to show a word list in the sidebar 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/resources/prefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part8/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 66 | 67 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | // Task to compile gresource files 20 | tasks.register('compileResources') { 21 | exec { 22 | workingDir 'src/main/resources' 23 | commandLine 'glib-compile-resources', 'exampleapp.gresource.xml' 24 | } 25 | } 26 | 27 | tasks.named('classes') { 28 | dependsOn compileResources 29 | } 30 | 31 | tasks.named('run') { 32 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 33 | args = ["ExampleApp", 34 | "src/main/java/ExampleMainClass.java", 35 | "src/main/java/ExampleApp.java", 36 | "src/main/java/ExampleAppWindow.java"] 37 | } 38 | 39 | application { 40 | mainClass = "ExampleMainClass" 41 | } 42 | 43 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/java/ExampleApp.java: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags; 2 | import org.gnome.gio.File; 3 | import org.gnome.gio.SimpleAction; 4 | import org.gnome.glib.List; 5 | import org.gnome.glib.Variant; 6 | import org.gnome.gtk.Application; 7 | import org.gnome.gtk.Window; 8 | 9 | public class ExampleApp extends Application { 10 | 11 | @Override 12 | public void activate() { 13 | ExampleAppWindow win = new ExampleAppWindow(this); 14 | win.present(); 15 | } 16 | 17 | @Override 18 | public void open(File[] files, String hint) { 19 | ExampleAppWindow win; 20 | List windows = super.getWindows(); 21 | if (!windows.isEmpty()) 22 | win = (ExampleAppWindow) windows.getFirst(); 23 | else 24 | win = new ExampleAppWindow(this); 25 | 26 | for (File file : files) 27 | win.open(file); 28 | 29 | win.present(); 30 | } 31 | 32 | public void preferencesActivated(Variant parameter) { 33 | ExampleAppWindow win = (ExampleAppWindow) getActiveWindow(); 34 | ExampleAppPrefs prefs = new ExampleAppPrefs(win); 35 | prefs.present(); 36 | } 37 | 38 | public void quitActivated(Variant parameter) { 39 | super.quit(); 40 | } 41 | 42 | @Override 43 | public void startup() { 44 | super.startup(); 45 | 46 | var preferences = new SimpleAction("preferences", null); 47 | preferences.onActivate(this::preferencesActivated); 48 | addAction(preferences); 49 | 50 | var quit = new SimpleAction("quit", null); 51 | quit.onActivate(this::quitActivated); 52 | addAction(quit); 53 | 54 | String[] quitAccels = new String[]{"q"}; 55 | setAccelsForAction("app.quit", quitAccels); 56 | } 57 | 58 | public ExampleApp() { 59 | setApplicationId("org.gtk.exampleapp"); 60 | setFlags(ApplicationFlags.HANDLES_OPEN); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/java/ExampleAppPrefs.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 2 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 3 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 4 | import org.gnome.gio.Settings; 5 | import org.gnome.gio.SettingsBindFlags; 6 | import org.gnome.gtk.ComboBoxText; 7 | import org.gnome.gtk.Dialog; 8 | import org.gnome.gtk.FontButton; 9 | 10 | @GtkTemplate(ui="/org/gtk/exampleapp/prefs.ui") 11 | public class ExampleAppPrefs extends Dialog { 12 | 13 | @GtkChild 14 | public FontButton font; 15 | 16 | @GtkChild 17 | public ComboBoxText transition; 18 | 19 | Settings settings; 20 | 21 | @InstanceInit 22 | public void init() { 23 | settings = new Settings("org.gtk.exampleapp"); 24 | settings.bind("font", font, "font", SettingsBindFlags.DEFAULT); 25 | settings.bind("transition", transition, "active-id", SettingsBindFlags.DEFAULT); 26 | } 27 | 28 | public ExampleAppPrefs(ExampleAppWindow win) { 29 | setTransientFor(win); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/java/ExampleAppWindow.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import io.github.jwharm.javagi.base.Out; 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkCallback; 5 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 6 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 7 | import org.gnome.gio.*; 8 | import org.gnome.gio.Settings; 9 | import org.gnome.gobject.BindingFlags; 10 | import org.gnome.gobject.ParamSpec; 11 | import org.gnome.gtk.*; 12 | 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | @GtkTemplate(ui="/org/gtk/exampleapp/window.ui") 17 | public class ExampleAppWindow extends ApplicationWindow { 18 | 19 | @GtkChild public Stack stack; 20 | @GtkChild public MenuButton gears; 21 | @GtkChild public ToggleButton search; 22 | @GtkChild public SearchBar searchbar; 23 | @GtkChild public SearchEntry searchentry; 24 | @GtkChild public Revealer sidebar; 25 | @GtkChild public ListBox words; 26 | @GtkChild public Label lines; 27 | @GtkChild public Label lines_label; 28 | 29 | private Settings settings; 30 | 31 | public ExampleAppWindow(ExampleApp app) { 32 | setApplication(app); 33 | } 34 | 35 | @InstanceInit 36 | public void init() { 37 | var builder = GtkBuilder.fromResource("/org/gtk/exampleapp/gears-menu.ui"); 38 | var menu = (MenuModel) builder.getObject("menu"); 39 | gears.setMenuModel(menu); 40 | 41 | settings = new Settings("org.gtk.exampleapp"); 42 | settings.bind("transition", stack, "transition-type", SettingsBindFlags.DEFAULT); 43 | search.bindProperty("active", searchbar, "search-mode-enabled", BindingFlags.BIDIRECTIONAL); 44 | 45 | settings.bind("show-words", sidebar, "reveal-child", SettingsBindFlags.DEFAULT); 46 | sidebar.onNotify("reveal-child", _ -> updateWords()); 47 | addAction(settings.createAction("show-words")); 48 | 49 | addAction(new PropertyAction("show-lines", lines, "visible")); 50 | lines.bindProperty("visible", lines_label, "visible", BindingFlags.DEFAULT); 51 | } 52 | 53 | public void open(File file) { 54 | String basename = file.getBasename(); 55 | 56 | var scrolled = new ScrolledWindow(); 57 | scrolled.setHexpand(true); 58 | scrolled.setVexpand(true); 59 | var view = new TextView(); 60 | view.setEditable(false); 61 | view.setCursorVisible(false); 62 | scrolled.setChild(view); 63 | stack.addTitled(scrolled, basename, basename); 64 | var buffer = view.getBuffer(); 65 | 66 | try { 67 | var contents = new Out(); 68 | if (file.loadContents(null, contents, null)) { 69 | String str = new String(contents.get()); 70 | buffer.setText(str, str.length()); 71 | } 72 | } catch (GErrorException e) { 73 | throw new RuntimeException(e); 74 | } 75 | 76 | var tag = buffer.createTag(null, null); 77 | settings.bind("font", tag, "font", SettingsBindFlags.DEFAULT); 78 | TextIter startIter = new TextIter(); 79 | TextIter endIter = new TextIter(); 80 | buffer.getStartIter(startIter); 81 | buffer.getEndIter(endIter); 82 | buffer.applyTag(tag, startIter, endIter); 83 | 84 | search.setSensitive(true); 85 | 86 | updateWords(); 87 | updateLines(); 88 | } 89 | 90 | @GtkCallback(name="search_text_changed") 91 | public void searchTextChanged() { 92 | String text = searchentry.getText(); 93 | 94 | if (text.isEmpty()) 95 | return; 96 | 97 | var tab = (ScrolledWindow) stack.getVisibleChild(); 98 | var view = (TextView) tab.getChild(); 99 | var buffer = view.getBuffer(); 100 | 101 | // Very simple-minded search implementation 102 | TextIter startIter = new TextIter(); 103 | TextIter matchStart = new TextIter(); 104 | TextIter matchEnd = new TextIter(); 105 | buffer.getStartIter(startIter); 106 | if (startIter.forwardSearch(text, TextSearchFlags.CASE_INSENSITIVE, 107 | matchStart, matchEnd, null)) { 108 | buffer.selectRange(matchStart, matchEnd); 109 | view.scrollToIter(matchStart, 0.0, false, 0.0, 0.0); 110 | } 111 | } 112 | 113 | @GtkCallback(name="visible_child_changed") 114 | public void visibleChildChanged(ParamSpec pspec) { 115 | if (stack.inDestruction()) 116 | return; 117 | 118 | searchbar.setSearchMode(false); 119 | updateWords(); 120 | updateLines(); 121 | } 122 | 123 | private void findWord(Button button) { 124 | String word = button.getLabel(); 125 | searchentry.setText(word); 126 | } 127 | 128 | private void updateWords() { 129 | var tab = (ScrolledWindow) stack.getVisibleChild(); 130 | if (tab == null) 131 | return; 132 | 133 | var view = (TextView) tab.getChild(); 134 | var buffer = view.getBuffer(); 135 | 136 | Set strings = new HashSet<>(); 137 | 138 | TextIter end, start = new TextIter(); 139 | buffer.getStartIter(start); 140 | 141 | outer: 142 | while (!start.isEnd()) { 143 | while (!start.startsWord()) 144 | if (!start.forwardChar()) 145 | break outer; 146 | 147 | end = start.copy(); 148 | if (!end.forwardWordEnd()) 149 | break; 150 | 151 | var word = buffer.getText(start, end, false); 152 | strings.add(word.toLowerCase()); 153 | start = end.copy(); 154 | } 155 | 156 | Widget child; 157 | while ((child = words.getFirstChild()) != null) 158 | words.remove(child); 159 | 160 | for (var key : strings) { 161 | var row = Button.withLabel(key); 162 | row.onClicked(() -> findWord(row)); 163 | words.insert(row, -1); 164 | } 165 | } 166 | 167 | private void updateLines() { 168 | var tab = (ScrolledWindow) stack.getVisibleChild(); 169 | if (tab == null) 170 | return; 171 | 172 | var view = (TextView) tab.getChild(); 173 | var buffer = view.getBuffer(); 174 | 175 | int count = buffer.getLineCount(); 176 | lines.setText("%d".formatted(count)); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/java/ExampleMainClass.java: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException; 2 | import org.gnome.gio.Resource; 3 | 4 | public class ExampleMainClass { 5 | 6 | public static void main(String[] args) throws GErrorException { 7 | var resource = Resource.load("src/main/resources/exampleapp.gresource"); 8 | resource.resourcesRegister(); 9 | 10 | new ExampleApp().run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/resources/exampleapp.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gears-menu.ui 6 | prefs.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/resources/gears-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | _Words 7 | win.show-words 8 | 9 | 10 | _Lines 11 | win.show-lines 12 | 13 | 14 | _Preferences 15 | app.preferences 16 | 17 |
18 |
19 | 20 | _Quit 21 | app.quit 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/resources/org.gtk.exampleapp.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 'Monospace 12' 6 | Font 7 | The font to be used for content. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'none' 16 | Transition 17 | The transition to use when switching tabs. 18 | 19 | 20 | false 21 | Show words 22 | Whether to show a word list in the sidebar 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/resources/prefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /GettingStarted/example-5-part9/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 77 | 78 | -------------------------------------------------------------------------------- /HelloTemplate/README.md: -------------------------------------------------------------------------------- 1 | ## Hello Template 2 | 3 | This example is a simple "Hello World" Gtk application using an XML template. 4 | It is inspired by the Vala project template generated by GNOME Builder. 5 | 6 | The ui resource files are located in the `src/main/resources/` folder. They 7 | need to be compiled with `glib-compile-resources` before they can be used. The 8 | Gradle build script is configured to run `glib-compile-resources` before the 9 | Java code is compiled and run, so please make sure the Gtk development tools 10 | are installed. 11 | 12 | The application can be built and run using `gradle run`. 13 | 14 | ### Flatpak installation 15 | 16 | The example can be installed as a Flatpak application. 17 | 18 | First, install the required Flatpak runtimes: 19 | ```shell 20 | flatpak --user install org.gnome.Platform//47 org.gnome.Sdk//47 org.freedesktop.Sdk.Extension.openjdk//24.08 21 | ``` 22 | 23 | Run the following shell commands: 24 | 25 | ```shell 26 | gradle flatpakGradleGenerator 27 | flatpak-builder --user --install build/flatpak flatpak/my.example.HelloTemplate.json 28 | ``` 29 | 30 | The first command scans the Gradle build for online dependencies, and generates 31 | a JSON file with all dependency URLs. The second command runs `flatpak-builder`. 32 | The Flatpak build script can be found in the `flatpak/` folder. It includes the 33 | generated JSON file (so the dependencies will be downloaded) and then runs 34 | `gradle installDist`. When done, you can run the application: 35 | 36 | ```shell 37 | flatpak run my.example.HelloTemplate 38 | ``` 39 | 40 | ![Hello World (template based) screenshot](template-helloworld.png) 41 | -------------------------------------------------------------------------------- /HelloTemplate/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | id 'io.github.jwharm.flatpak-gradle-generator' version "1.5.0" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | maven { url='build/repository' } // used by flatpak-builder 9 | } 10 | 11 | dependencies { 12 | implementation 'io.github.jwharm.javagi:adw:0.12.2' 13 | } 14 | 15 | // Task to compile gresource files 16 | tasks.register('compileResources') { 17 | exec { 18 | workingDir 'src/main/resources' 19 | commandLine 'glib-compile-resources', 'helloworld.gresource.xml' 20 | } 21 | } 22 | 23 | // Task to generate a file with all dependency urls for the offline flatpak build 24 | tasks.flatpakGradleGenerator { 25 | outputFile.set(file("$rootDir/flatpak/maven-dependencies.json")) 26 | downloadDirectory.set('build/repository') 27 | } 28 | 29 | java { 30 | toolchain { 31 | languageVersion = JavaLanguageVersion.of(23) 32 | } 33 | } 34 | 35 | tasks.named('compileJava') { 36 | dependsOn compileResources 37 | } 38 | 39 | tasks.named('installDist') { 40 | destinationDir = file('/app/HelloTemplate') 41 | } 42 | 43 | application { 44 | mainClass = "my.example.hellotemplate.Main" 45 | applicationDefaultJvmArgs += "--enable-native-access=ALL-UNNAMED" 46 | } 47 | -------------------------------------------------------------------------------- /HelloTemplate/flatpak/my.example.HelloTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "my.example.HelloTemplate", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "47", 5 | "sdk" : "org.gnome.Sdk", 6 | "sdk-extensions" : [ 7 | "org.freedesktop.Sdk.Extension.openjdk" 8 | ], 9 | "command" : "/app/HelloTemplate/bin/my.example.HelloTemplate", 10 | "finish-args" : [ 11 | "--socket=fallback-x11", 12 | "--socket=wayland", 13 | "--device=dri", 14 | "--share=ipc", 15 | "--share=network", 16 | "--filesystem=home", 17 | "--env=JAVA_HOME=/app/jre" 18 | ], 19 | "modules" : [ 20 | { 21 | "name" : "jre", 22 | "buildsystem" : "simple", 23 | "build-commands" : [ "/usr/lib/sdk/openjdk/install.sh" ] 24 | }, 25 | { 26 | "name" : "my.example.HelloTemplate", 27 | "buildsystem" : "simple", 28 | "build-commands" : [ "gradle installDist" ], 29 | "build-options" : { 30 | "append-path": "/usr/lib/sdk/openjdk/gradle/bin:/usr/lib/sdk/openjdk/jvm/openjdk-23/bin" 31 | }, 32 | "sources" : [ 33 | { "type" : "dir", "path" : ".." }, 34 | "maven-dependencies.json" 35 | ] 36 | } 37 | ] 38 | } 39 | 40 | -------------------------------------------------------------------------------- /HelloTemplate/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { url='build/repository' } // used by flatpak-builder 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/java/my/example/hellotemplate/HelloApplication.java: -------------------------------------------------------------------------------- 1 | package my.example.hellotemplate; 2 | 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.gobject.annotations.RegisteredType; 5 | import org.gnome.adw.AboutDialog; 6 | import org.gnome.adw.Application; 7 | import org.gnome.gio.ApplicationFlags; 8 | import org.gnome.gio.SimpleAction; 9 | import org.gnome.glib.Variant; 10 | 11 | /** 12 | * HelloApplication is derived from AdwApplication. The class is registered as 13 | * a new GType. 14 | *

15 | * The method {@link #activate()} is registered in the GTypeClass 16 | * as a virtual method, so the GType system will call it when the application 17 | * is activated. 18 | *

19 | * The method {@link #init()} is called during instance initialization of 20 | * the new GObject instance. 21 | */ 22 | @RegisteredType(name="HelloApplication") 23 | public class HelloApplication extends Application { 24 | /** 25 | * This is the constructor of the HelloApplication class. When the instance 26 | * is created the {@link #init()} method is automatically run. 27 | */ 28 | public HelloApplication() { 29 | setApplicationId("my.example.HelloTemplate"); 30 | setFlags(ApplicationFlags.DEFAULT_FLAGS); 31 | } 32 | 33 | /** 34 | * This method is called during construction of the new GObject instance. 35 | * The name "init" can be freely chosen; the {@code @InstanceInit} annotation 36 | * marks it as an instance init function. 37 | */ 38 | @InstanceInit 39 | public void init() { 40 | var about = new SimpleAction("about", null); 41 | about.onActivate(this::onAboutAction); 42 | addAction(about); 43 | 44 | var preferences = new SimpleAction("preferences", null); 45 | preferences.onActivate(this::onPreferencesAction); 46 | addAction(preferences); 47 | 48 | var greet = new SimpleAction("greet", null); 49 | greet.onActivate(this::onGreetAction); 50 | addAction(greet); 51 | 52 | var quit = new SimpleAction("quit", null); 53 | quit.onActivate(_ -> quit()); 54 | addAction(quit); 55 | setAccelsForAction("app.quit", new String[]{"q"}); 56 | } 57 | 58 | /** 59 | * Virtual method overrides are automatically registered in the GObject type class. 60 | * This means the activate() method is automatically recognized by Gtk and will be 61 | * executed during application startup. 62 | */ 63 | @Override 64 | public void activate() { 65 | var win = this.getActiveWindow(); 66 | if (win == null) { 67 | win = new HelloWindow(this); 68 | } 69 | win.present(); 70 | } 71 | 72 | // The following methods are executed by the ActionEntries defined above. 73 | 74 | private void onAboutAction(Variant parameter) { 75 | String[] developers = { "John Doe", "Jane Doe" }; 76 | var about = AboutDialog.builder() 77 | .setApplicationName("HelloTemplate") 78 | .setApplicationIcon("my.example.HelloTemplate") 79 | .setDeveloperName("James Random Hacker") 80 | .setDevelopers(developers) 81 | .setVersion("0.1.0") 82 | .setCopyright("© 2023 Yoyodyne, Inc") 83 | .build(); 84 | about.present(this.getActiveWindow()); 85 | } 86 | 87 | private void onPreferencesAction(Variant parameter) { 88 | System.out.println("app.preferences action activated"); 89 | } 90 | 91 | private void onGreetAction(Variant parameter) { 92 | var win = (HelloWindow) this.getActiveWindow(); 93 | if (win != null) { 94 | win.label.setLabel("Hello again!"); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/java/my/example/hellotemplate/HelloWindow.java: -------------------------------------------------------------------------------- 1 | package my.example.hellotemplate; 2 | 3 | import io.github.jwharm.javagi.gtk.annotations.GtkChild; 4 | import io.github.jwharm.javagi.gtk.annotations.GtkTemplate; 5 | import org.gnome.adw.ApplicationWindow; 6 | import org.gnome.gtk.Application; 7 | import org.gnome.gtk.Label; 8 | 9 | /** 10 | * The {@code @GtkTemplate} annotation marks HelloWindow as a Gtk composite 11 | * template class. The user interface is defined in the ui file, and the 12 | * application logic is implemented in Java. 13 | */ 14 | @GtkTemplate(name="HelloWindow", ui="/my/example/window.ui") 15 | public class HelloWindow extends ApplicationWindow { 16 | /** 17 | * This field is set to the GtkLabel instance defined in the ui file. 18 | */ 19 | @GtkChild 20 | public Label label; 21 | 22 | /** 23 | * Construct a new HelloWindow instance. 24 | * @param app the HelloApplication instance 25 | */ 26 | public HelloWindow (Application app) { 27 | setApplication(app); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/java/my/example/hellotemplate/Main.java: -------------------------------------------------------------------------------- 1 | package my.example.hellotemplate; 2 | 3 | import io.github.jwharm.javagi.base.GErrorException; 4 | import org.gnome.gio.Resource; 5 | 6 | /** 7 | * The Main class registers the compiled gresource bundle and 8 | * runs a new HelloApplication instance. 9 | */ 10 | public class Main { 11 | 12 | /** 13 | * Run the HelloTemplate example 14 | * @param args passed to AdwApplication.run() 15 | * @throws GErrorException thrown while loading and registering the 16 | * compiled resource bundle 17 | */ 18 | public static void main(String[] args) throws GErrorException { 19 | var resource = Resource.load("src/main/resources/helloworld.gresource"); 20 | resource.resourcesRegister(); 21 | 22 | var app = new HelloApplication(); 23 | app.run(args); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/resources/gtk/help-overlay.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | 6 | 7 | shortcuts 8 | 10 9 | 10 | 11 | General 12 | 13 | 14 | Show Shortcuts 15 | win.show-help-overlay 16 | 17 | 18 | 19 | 20 | Quit 21 | app.quit 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/resources/helloworld.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | gtk/help-overlay.ui 6 | 7 | 8 | -------------------------------------------------------------------------------- /HelloTemplate/src/main/resources/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 |

34 |
35 | 36 | _Preferences 37 | app.preferences 38 | 39 | 40 | _Keyboard Shortcuts 41 | win.show-help-overlay 42 | 43 | 44 | _Say hello! 45 | app.greet 46 | 47 | 48 | _About HelloWorld 49 | app.about 50 | 51 |
52 |
53 | 54 | -------------------------------------------------------------------------------- /HelloTemplate/template-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/HelloTemplate/template-helloworld.png -------------------------------------------------------------------------------- /HelloWorld/README.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | This example is a simple "Hello World" Gtk application. 4 | 5 | To run the example, clone the repository, navigate to the `HelloWorld` folder, and execute `gradle run`. 6 | 7 | ![Hello World screenshot](simple-helloworld.png) 8 | -------------------------------------------------------------------------------- /HelloWorld/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "io.github.jwharm.javagi.examples.helloworld.HelloWorld" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /HelloWorld/simple-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/HelloWorld/simple-helloworld.png -------------------------------------------------------------------------------- /HelloWorld/src/main/java/io/github/jwharm/javagi/examples/helloworld/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.helloworld; 2 | 3 | import org.gnome.gtk.*; 4 | import org.gnome.gio.ApplicationFlags; 5 | 6 | public class HelloWorld { 7 | 8 | public static void main(String[] args) { 9 | new HelloWorld(args); 10 | } 11 | 12 | private final Application app; 13 | 14 | public HelloWorld(String[] args) { 15 | app = new Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS); 16 | app.onActivate(this::activate); 17 | app.run(args); 18 | } 19 | 20 | public void activate() { 21 | var window = new ApplicationWindow(app); 22 | window.setTitle("GTK from Java"); 23 | window.setDefaultSize(300, 200); 24 | 25 | var box = Box.builder() 26 | .setOrientation(Orientation.VERTICAL) 27 | .setHalign(Align.CENTER) 28 | .setValign(Align.CENTER) 29 | .build(); 30 | 31 | var button = Button.withLabel("Hello world!"); 32 | button.onClicked(window::close); 33 | 34 | box.append(button); 35 | window.setChild(box); 36 | window.present(); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /HelloWorldScala/README.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | This example is a simple "Hello World" Gtk application. 4 | 5 | To run the example, clone the repository, navigate to the `HelloWorld` folder, and execute `sbt run`. 6 | 7 | ![Hello World screenshot](simple-helloworld.png) -------------------------------------------------------------------------------- /HelloWorldScala/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "3.3.4" 4 | 5 | lazy val root = (project in file(".")) 6 | .settings( 7 | name := "HelloWorldScala", 8 | fork := true, 9 | javaOptions += "--enable-native-access=ALL-UNNAMED" 10 | ) 11 | 12 | libraryDependencies += "io.github.jwharm.javagi" % "gtk" % "0.12.2" 13 | -------------------------------------------------------------------------------- /HelloWorldScala/simple-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/HelloWorldScala/simple-helloworld.png -------------------------------------------------------------------------------- /HelloWorldScala/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | import org.gnome.gio.ApplicationFlags 2 | import org.gnome.gtk.{Align, Application, ApplicationWindow, Box, Button, HeaderBar, Orientation} 3 | 4 | object HelloWorld: 5 | def main(args: Array[String] = Array()): Unit = new Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS) { 6 | onActivate(() => HelloWorld().activate(this)) 7 | }.run(args) 8 | 9 | class HelloWorld: 10 | private def activate(app: Application): Unit = 11 | lazy val window: ApplicationWindow = new ApplicationWindow(app) { 12 | setTitle("Gtk from Scala") 13 | setDefaultSize(300, 200) 14 | setChild(box) 15 | setTitlebar(HeaderBar()) 16 | } 17 | 18 | lazy val box: Box = new Box(Orientation.VERTICAL, 1) { 19 | val button: Button = new Button() { 20 | setLabel("Hello, world!") 21 | onClicked(() => window.close()) 22 | } 23 | 24 | setHalign(Align.CENTER) 25 | setValign(Align.CENTER) 26 | append(button) 27 | } 28 | 29 | window.present() -------------------------------------------------------------------------------- /ImageViewer/README.md: -------------------------------------------------------------------------------- 1 | ## ImageViewer 2 | 3 | This example is a basic Adwaita image viewer written in Scala. 4 | 5 | To run the example, clone the repository, navigate to the `ImageViewer` folder, and execute `sbt run`. 6 | 7 | ![Image Viewer Screenshot](image-viewer.png) 8 | -------------------------------------------------------------------------------- /ImageViewer/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "3.3.4" 4 | 5 | lazy val root = (project in file(".")) 6 | .settings( 7 | name := "ImageViewer", 8 | fork := true, 9 | javaOptions += "--enable-native-access=ALL-UNNAMED" 10 | ) 11 | 12 | libraryDependencies += "io.github.jwharm.javagi" % "gtk" % "0.12.2" 13 | libraryDependencies += "io.github.jwharm.javagi" % "adw" % "0.12.2" 14 | -------------------------------------------------------------------------------- /ImageViewer/image-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/ImageViewer/image-viewer.png -------------------------------------------------------------------------------- /ImageViewer/src/main/scala/App.scala: -------------------------------------------------------------------------------- 1 | import org.gnome.adw.Application 2 | import org.gnome.gio.ApplicationFlags 3 | 4 | class App extends Application("org.poach3r.Images", ApplicationFlags.DEFAULT_FLAGS): 5 | onActivate { () => 6 | Window(this).present() 7 | } -------------------------------------------------------------------------------- /ImageViewer/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | object Main: 2 | def main(args: Array[String]): Unit = App().run(args) 3 | -------------------------------------------------------------------------------- /ImageViewer/src/main/scala/Window.scala: -------------------------------------------------------------------------------- 1 | import io.github.jwharm.javagi.base.GErrorException 2 | import org.gnome.adw.{ApplicationWindow, HeaderBar} 3 | import org.gnome.gio.AsyncResult 4 | import org.gnome.gobject.GObject 5 | import org.gnome.gtk.{Picture, Button, Box, Orientation, FileDialog, FileFilter} 6 | 7 | import java.lang.foreign.MemorySegment 8 | 9 | class Window(app: App) extends ApplicationWindow(app): 10 | this.setTitle("None") 11 | this.setContent(content(Picture())) 12 | 13 | private def header(): HeaderBar = 14 | val openButton = new Button() { 15 | setIconName("document-open-symbolic") 16 | addCssClass("suggested-action") 17 | onClicked { () => 18 | openFileDialogue() 19 | } 20 | } 21 | 22 | val header = HeaderBar() 23 | header.packStart(openButton) 24 | header 25 | 26 | private def content(pic: Picture): Box = new Box(Orientation.VERTICAL, 0) { 27 | append(header()) 28 | append(pic) 29 | } 30 | 31 | private def openFileDialogue(): Unit = 32 | val dialog = FileDialog.builder() 33 | .setDefaultFilter(FileFilter.builder() 34 | .setSuffixes(Array("png", "jpg", "jpeg", "webp", "svg")) 35 | .build() 36 | ).build() 37 | 38 | dialog.open(this, null, (_: GObject, result: AsyncResult, _: MemorySegment) => { 39 | try 40 | val pic = Picture.forFile(dialog.openFinish(result)) 41 | setContent(content(pic)) 42 | setTitle(pic.getFile.getBasename) 43 | catch case _: GErrorException => println("Failed to find file.") 44 | }) -------------------------------------------------------------------------------- /Javascript/README.md: -------------------------------------------------------------------------------- 1 | ## Javascript callback 2 | 3 | This example is a small WebkitGtk application that runs a Java callback function from a Javascript function in the webpage. It is based on the JavascriptCallback sample in Gir.Core. 4 | 5 | To run the example, clone the repository, navigate to the `Javascript` folder, and execute `gradle run`. 6 | 7 | ![Javascript example screenshot](javascript.png) 8 | -------------------------------------------------------------------------------- /Javascript/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:adw:0.12.2' 11 | implementation 'io.github.jwharm.javagi:webkit:0.12.2' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.javascript.JavascriptExample" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Javascript/javascript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/Javascript/javascript.png -------------------------------------------------------------------------------- /Javascript/src/main/java/io/github/jwharm/javagi/examples/javascript/JavascriptExample.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.javascript; 2 | 3 | import io.github.jwharm.javagi.base.UnsupportedPlatformException; 4 | import org.gnome.gtk.*; 5 | import org.gnome.gio.ApplicationFlags; 6 | import org.gnome.webkit.*; 7 | import org.gnome.webkit.jsc.Value; 8 | 9 | /** 10 | * Small example app that demonstrates a callback from a Javascript script running 11 | * in a WebView that calls a Java method. 12 | */ 13 | public class JavascriptExample { 14 | 15 | public static void main(String[] args) { 16 | new JavascriptExample(args); 17 | } 18 | 19 | private final Application app; 20 | private ApplicationWindow window; 21 | 22 | // The javascript 23 | private static final String script = """ 24 | (function(globalContext) { 25 | globalContext.document.getElementById("inputId").onclick = function () { 26 | var message = { theMessage : "Sent from Javascript" }; 27 | window.webkit.messageHandlers["handlerId"].postMessage(message); 28 | }; 29 | })(this) 30 | """; 31 | 32 | // The webpage 33 | private static final String html = """ 34 | 35 | 36 | Javascript Demo 37 | 38 |

Javascript callback demo

39 | Welcome to this webpage. 40 |

41 | If you click the following button, a 42 | GtkAlertDialog 43 | will open:
44 | 45 |

46 | Thanks for visiting. 47 | 48 | 49 | """; 50 | 51 | public JavascriptExample(String[] args) { 52 | app = new Application("io.github.jwharm.javagi.examples.Javascript", ApplicationFlags.DEFAULT_FLAGS); 53 | app.onActivate(this::activate); 54 | app.run(args); 55 | } 56 | 57 | public void activate() { 58 | try { 59 | // Create window 60 | window = new ApplicationWindow(app); 61 | window.setTitle("WebKit Demo"); 62 | 63 | // Create webview 64 | WebView webview = WebView.builder() 65 | .setHeightRequest(300) 66 | .setWidthRequest(500) 67 | .build(); 68 | 69 | // Get the usercontent manager and add the javascript 70 | var manager = webview.getUserContentManager(); 71 | manager.addScript(new UserScript( 72 | script, 73 | UserContentInjectedFrames.ALL_FRAMES, 74 | UserScriptInjectionTime.END, 75 | null, 76 | null 77 | )); 78 | 79 | // Connect the javascript call to a Java callback function 80 | manager.onScriptMessageReceived(null, this::displayMessage); 81 | manager.registerScriptMessageHandler("handlerId", null); 82 | 83 | // Load the webview page and display the results 84 | webview.loadHtml(html, null); 85 | window.setChild(webview); 86 | window.present(); 87 | 88 | } catch (UnsupportedPlatformException e) { 89 | System.out.println("Not supported on this platform"); 90 | } 91 | } 92 | 93 | /* 94 | * This is the callback method that is triggered from javascript. 95 | * The Value parameter is a JSCValue, not a GValue. 96 | */ 97 | private void displayMessage(Value value) { 98 | if (! value.isObject()) { 99 | System.out.println("Returned value is expected to be an object"); 100 | return; 101 | } 102 | var result = value.objectGetProperty("theMessage"); 103 | AlertDialog dialog = AlertDialog.builder() 104 | .setMessage("Message received: " + result.toString()) 105 | .build(); 106 | dialog.show(window); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ListViewer/README.md: -------------------------------------------------------------------------------- 1 | ## List Viewer 2 | 3 | This example displays a long, scrollable list with randomly generated words. It demonstrates how you can use a Java ArrayList to implement the GListModel interface, which is central to all modern Gtk list widgets. 4 | 5 | To run the example, clone the repository, navigate to the `ListViewer` folder, and execute `gradle run`. 6 | 7 | ![ListViewer screenshot](listviewer.png) 8 | -------------------------------------------------------------------------------- /ListViewer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "io.github.jwharm.javagi.examples.listviewer.ListViewer" 25 | } 26 | -------------------------------------------------------------------------------- /ListViewer/listviewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/ListViewer/listviewer.png -------------------------------------------------------------------------------- /ListViewer/src/main/java/io/github/jwharm/javagi/examples/listviewer/ListViewer.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.listviewer; 2 | 3 | import io.github.jwharm.javagi.gio.ListIndexModel; 4 | import org.gnome.gio.ApplicationFlags; 5 | import org.gnome.gtk.*; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Random; 10 | 11 | public class ListViewer extends Application { 12 | 13 | private final List list; 14 | private final ListIndexModel listIndexModel; 15 | private final Random rnd = new Random(); 16 | 17 | public void activate() { 18 | var window = new ApplicationWindow(this); 19 | window.setTitle("ListViewer example"); 20 | window.setDefaultSize(300, 500); 21 | 22 | var box = new Box(Orientation.VERTICAL, 0); 23 | 24 | SignalListItemFactory factory = new SignalListItemFactory(); 25 | factory.onSetup(object -> { 26 | ListItem listitem = (ListItem) object; 27 | listitem.setChild(new Label("")); 28 | }); 29 | factory.onBind(object -> { 30 | ListItem listitem = (ListItem) object; 31 | Label label = (Label) listitem.getChild(); 32 | ListIndexModel.ListIndex item = (ListIndexModel.ListIndex) listitem.getItem(); 33 | if (label == null || item == null) 34 | return; 35 | 36 | // The ListIndexModel contains ListIndexItems that contain only their index in the list. 37 | int index = item.getIndex(); 38 | 39 | // Retrieve the index of the item and show the entry from the ArrayList with random strings. 40 | String text = list.get(index); 41 | label.setLabel(text); 42 | }); 43 | 44 | ScrolledWindow scroll = new ScrolledWindow(); 45 | ListView lv = new ListView(new SingleSelection<>(listIndexModel), factory); 46 | scroll.setChild(lv); 47 | scroll.setVexpand(true); 48 | box.append(scroll); 49 | 50 | window.setChild(box); 51 | window.present(); 52 | } 53 | 54 | // Generate a short random string 55 | private String randomString() { 56 | StringBuilder sb = new StringBuilder(); 57 | for (int i = 0, len = rnd.nextInt(5, 10); i < len; i++) { 58 | sb.append((char) rnd.nextInt('a', 'z' + 1)); 59 | } 60 | return sb.toString(); 61 | } 62 | 63 | public ListViewer(String[] args) { 64 | super("io.github.jwharm.javagi.example.ListView", ApplicationFlags.FLAGS_NONE); 65 | 66 | // Build a list with many (between 500 and 1000) random strings. 67 | // The list is a normal java ArrayList, nothing special. 68 | list = new ArrayList<>(); 69 | for (int i = 0, len = rnd.nextInt(500, 1000); i < len; i++) { 70 | list.add(randomString()); 71 | } 72 | listIndexModel = new ListIndexModel(list.size()); 73 | 74 | onActivate(this::activate); 75 | run(args); 76 | } 77 | 78 | public static void main(String[] args) { 79 | new ListViewer(args); 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Logging/README.md: -------------------------------------------------------------------------------- 1 | ## Logging adapter 2 | 3 | This is a small experiment to redirect the output of the GLib logging functions to 4 | the Java logging framework SLF4J. 5 | 6 | To run the example, clone the repository, navigate to the `Logging` folder, and execute `gradle run`. 7 | -------------------------------------------------------------------------------- /Logging/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'org.slf4j:slf4j-simple:2.0.13' 11 | implementation 'io.github.jwharm.javagi:glib:0.12.2' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.logging.Logging" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Logging/src/main/java/io/github/jwharm/javagi/examples/logging/Logging.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.logging; 2 | 3 | import org.gnome.glib.GLib; 4 | 5 | import static org.gnome.glib.LogLevelFlags.*; 6 | 7 | /** 8 | * Register a custom GLogWriterFunc that uses SLF4J for logging. 9 | */ 10 | public class Logging { 11 | 12 | public static void main(String[] args) { 13 | GLib.logSetWriterFunc(new SLF4JLogWriterFunc()); 14 | 15 | GLib.log("logging-example", LEVEL_MESSAGE, "Hello %s\n", "world"); 16 | GLib.log("logging-example", LEVEL_MESSAGE, "%d + %d = %d\n", 1, 1, 2); 17 | GLib.log("logging-example", LEVEL_WARNING, "This is a warning\n"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Logging/src/main/java/io/github/jwharm/javagi/examples/logging/SLF4JLogWriterFunc.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.logging; 2 | 3 | import org.gnome.glib.GLib; 4 | import org.gnome.glib.LogField; 5 | import org.gnome.glib.LogLevelFlags; 6 | import org.gnome.glib.LogWriterFunc; 7 | import org.gnome.glib.LogWriterOutput; 8 | import org.slf4j.LoggerFactory; 9 | import org.slf4j.event.Level; 10 | 11 | import java.lang.foreign.MemorySegment; 12 | import java.util.Set; 13 | 14 | /** 15 | * Example GLogWriteFunc implementation that outputs GLib logging to SLF4J. 16 | *

17 | * To be used with {@link GLib#logSetWriterFunc}. 18 | *

19 | * Libraries must not use this class — only programs are 20 | * allowed to install a GLib log writer function, as there must be a single, 21 | * central point where log messages are formatted and outputted. 22 | */ 23 | public final class SLF4JLogWriterFunc implements LogWriterFunc { 24 | 25 | @Override 26 | public LogWriterOutput run(Set flags, LogField[] logFields) { 27 | try { 28 | String domain = readStringFromKey("GLIB_DOMAIN", logFields); 29 | String message = readStringFromKey("MESSAGE", logFields); 30 | Level level = convertLevel(flags.iterator().next()); 31 | LoggerFactory.getLogger(domain).atLevel(level).log(message); 32 | return LogWriterOutput.HANDLED; 33 | } catch (Exception e) { 34 | return LogWriterOutput.UNHANDLED; 35 | } 36 | } 37 | 38 | // Map GLib log level to SLF4J log level 39 | private static Level convertLevel(LogLevelFlags flag) { 40 | return switch (flag) { 41 | case LEVEL_CRITICAL, LEVEL_ERROR -> Level.ERROR; 42 | case LEVEL_WARNING -> Level.WARN; 43 | case LEVEL_MESSAGE, LEVEL_INFO -> Level.INFO; 44 | case LEVEL_DEBUG -> Level.DEBUG; 45 | default -> throw new IllegalArgumentException( 46 | "Unsupported LogLevelFlag: " + flag); 47 | }; 48 | } 49 | 50 | // Find the field with the requested key and return the String value 51 | private static String readStringFromKey(String key, LogField[] logFields) { 52 | for (var field : logFields) { 53 | String k = field.readKey(); 54 | if (k.equals(key)) 55 | return readString(field); 56 | } 57 | throw new IllegalArgumentException("Cannot find key " + key); 58 | } 59 | 60 | // Read a String value from a field 61 | private static String readString(LogField field) { 62 | long length = field.readLength(); 63 | MemorySegment value = field.readValue(); 64 | if (length == -1) { 65 | value = value.reinterpret(Long.MAX_VALUE); 66 | return value.getString(0); 67 | } else { 68 | value = value.reinterpret(length); 69 | return value.getString(0); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MediaStream/README.md: -------------------------------------------------------------------------------- 1 | ## Media Stream 2 | 3 | This example is a simple Gtk application that draws images using Cairo inside a GtkMediaStream. It is 4 | ported from the GtkDemo "Media Stream" example (in the "Paintable" category). To draw the graphics, the [Cairo Java bindings](https://github.com/jwharm/cairo-java-bindings) are used. 5 | 6 | To run the example, clone the repository, navigate to the `MediaStream` folder, and execute `gradle run`. 7 | 8 | ![Media Stream screenshot](mediastream.png) 9 | -------------------------------------------------------------------------------- /MediaStream/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | implementation 'io.github.jwharm.cairobindings:cairo:1.18.4.1' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.mediastream.Animation" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /MediaStream/mediastream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/MediaStream/mediastream.png -------------------------------------------------------------------------------- /Notepad/README.md: -------------------------------------------------------------------------------- 1 | ## Notepad 2 | 3 | This example is a small Gtk application to edit text in a Gtk `TextView` component. You can create, save and load files with the buttons in the `HeaderBar`. 4 | 5 | To run the example, clone the repository, navigate to the `Notepad` folder, and execute `gradle run`. 6 | 7 | ![Notepad screenshot](notepad.png) 8 | -------------------------------------------------------------------------------- /Notepad/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "io.github.jwharm.javagi.examples.notepad.Notepad" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Notepad/notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/Notepad/notepad.png -------------------------------------------------------------------------------- /Notepad/src/main/java/io/github/jwharm/javagi/examples/notepad/EditorWindow.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.notepad; 2 | 3 | import io.github.jwharm.javagi.gobject.annotations.InstanceInit; 4 | import io.github.jwharm.javagi.base.GErrorException; 5 | import io.github.jwharm.javagi.base.Out; 6 | import org.gnome.gio.File; 7 | import org.gnome.gio.FileCreateFlags; 8 | import org.gnome.gtk.*; 9 | 10 | /** 11 | * The EditorWindow class contains a headerbar and the textview. The headerbar 12 | * contains buttons for new/open/save actions. 13 | */ 14 | public class EditorWindow extends ApplicationWindow { 15 | 16 | // The currently open file (or null when no file is open) 17 | private File file = null; 18 | 19 | // The textview component 20 | private TextView textview; 21 | 22 | // Constructor for a new EditorWindow 23 | public EditorWindow(Application application) { 24 | setApplication(application); 25 | present(); 26 | 27 | // Make sure the text field has the keyboard focus. 28 | textview.grabFocus(); 29 | } 30 | 31 | /** 32 | * The @InstanceInit method is called during construction of a new EditorWindow instance. 33 | * It creates the window layout (the headerbar, buttons, and textview). 34 | */ 35 | @InstanceInit 36 | public void init() { 37 | 38 | // Create the headerbar 39 | var header = new HeaderBar(); 40 | super.setTitlebar(header); 41 | 42 | // Create the TextView and set the font style to monospace 43 | textview = new TextView(); 44 | textview.setMonospace(true); 45 | 46 | // The window title contains a "modified" indicator, that is 47 | // updated by the "on-modified-change" signal of the text buffer. 48 | textview.getBuffer().onModifiedChanged(this::updateWindowTitle); 49 | 50 | // The textView should be scrollable. 51 | var scrolledWindow = ScrolledWindow.builder() 52 | .setChild(textview) 53 | .setVexpand(true) 54 | .build(); 55 | super.setChild(scrolledWindow); 56 | 57 | // Create buttons for 'new', 'open' and 'save' actions. 58 | var newButton = Button.fromIconName("document-new-symbolic"); 59 | newButton.onClicked(() -> whenSure(this::clear)); 60 | header.packStart(newButton); 61 | 62 | var openButton = Button.fromIconName("document-open-symbolic"); 63 | openButton.onClicked(() -> whenSure(this::open)); 64 | header.packStart(openButton); 65 | 66 | var saveButton = Button.fromIconName("document-save-symbolic"); 67 | saveButton.onClicked(this::save); 68 | header.packStart(saveButton); 69 | 70 | // Ask to save changes before closing the window. 71 | this.onCloseRequest(() -> { 72 | whenSure(this::destroy); 73 | return true; 74 | }); 75 | 76 | // Show the results. 77 | updateWindowTitle(); 78 | } 79 | 80 | /** 81 | * Updates the window title to a modified-indicator and the current filename. 82 | * When no file is open, the title is "Unnamed". 83 | */ 84 | private void updateWindowTitle() { 85 | super.setTitle( 86 | (textview.getBuffer().getModified() ? ("• ") : "") + 87 | (file == null ? "Unnamed" : file.getBasename()) 88 | ); 89 | } 90 | 91 | /** 92 | * Runs {@code action} but, if the buffer is modified, asks to save 93 | * the modifications first. 94 | * 95 | * @param action the action to run after saving the modifications 96 | */ 97 | private void whenSure(Runnable action) { 98 | // No modifications? 99 | if (! textview.getBuffer().getModified()) { 100 | action.run(); 101 | return; 102 | } 103 | 104 | // Set up the confirmation dialog with three buttons: 105 | // 0 - Cancel, 1 - Discard, 2 - Save 106 | AlertDialog alert = AlertDialog.builder() 107 | .setModal(true) 108 | .setMessage("Save changes?") 109 | .setDetail("Do you want to save your changes?") 110 | .setButtons(new String[] {"Cancel", "Discard", "Save"}) 111 | .setCancelButton(0) 112 | .setDefaultButton(2) 113 | .build(); 114 | 115 | // Get dialog result 116 | alert.choose(this, null, (_, result, _) -> { 117 | try { 118 | int button = alert.chooseFinish(result); 119 | if (button == 0) return; // cancel 120 | if (button == 2) save(); // save 121 | action.run(); 122 | } catch (GErrorException ignored) {} // user clicked cancel 123 | }); 124 | } 125 | 126 | /** 127 | * "New" action: clear the editor buffer. 128 | * This could also open a new window, instead of cleaning the current buffer. 129 | */ 130 | public void clear() { 131 | file = null; 132 | textview.getBuffer().setText("", 0); 133 | textview.getBuffer().setModified(false); 134 | textview.grabFocus(); 135 | } 136 | 137 | /** 138 | * "Open" action: Load a file and show the contents in the editor. 139 | */ 140 | public void open() { 141 | // Set up an Open File dialog. 142 | var dialog = new FileDialog(); 143 | dialog.open(this, null, (_, result, _) -> { 144 | try { 145 | file = dialog.openFinish(result); 146 | } catch (GErrorException ignored) {} // used clicked cancel 147 | if (file == null) return; 148 | 149 | // Load the contents of the selected file. 150 | try { 151 | // The byte[] parameter is an out-parameter in the C API. 152 | // Create an empty Out object, and read its value afterward. 153 | Out contents = new Out<>(); 154 | file.loadContents(null, contents, null); 155 | textview.getBuffer().setText(new String(contents.get()), contents.get().length); 156 | textview.getBuffer().setModified(false); 157 | textview.grabFocus(); 158 | } catch (GErrorException e) { 159 | AlertDialog.builder() 160 | .setModal(true) 161 | .setMessage("Error reading from file") 162 | .setDetail(e.getMessage()) 163 | .build() 164 | .show(this); 165 | } 166 | }); 167 | } 168 | 169 | /** 170 | * "Save" action: Show a file dialog (for new files) and call {@link #write()} 171 | */ 172 | public void save() { 173 | if (file == null) { 174 | // Set up a Save File dialog. 175 | var dialog = new FileDialog(); 176 | dialog.save(this, null, (_, result, _) -> { 177 | try { 178 | file = dialog.saveFinish(result); 179 | if (file == null) return; 180 | 181 | // Write the textview buffer contents to the selected file. 182 | write(); 183 | textview.getBuffer().setModified(false); 184 | textview.grabFocus(); 185 | } catch (GErrorException ignored) {} // used clicked cancel 186 | }); 187 | } else { 188 | // Write the textview buffer contents to the file that was already open. 189 | write(); 190 | textview.getBuffer().setModified(false); 191 | textview.grabFocus(); 192 | } 193 | } 194 | 195 | /** 196 | * Helper function that writes editor contents to a file. 197 | */ 198 | private void write() { 199 | try { 200 | // Get the contents of the textview buffer as a byte array 201 | TextIter start = new TextIter(); 202 | TextIter end = new TextIter(); 203 | textview.getBuffer().getBounds(start, end); 204 | byte[] contents = textview.getBuffer().getText(start, end, false).getBytes(); 205 | 206 | // Write the byte array to the file 207 | file.replaceContents(contents, "", false, FileCreateFlags.NONE, null, null); 208 | } catch (GErrorException e) { 209 | AlertDialog.builder() 210 | .setModal(true) 211 | .setMessage("Error writing to file") 212 | .setDetail(e.getMessage()) 213 | .build() 214 | .show(this); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Notepad/src/main/java/io/github/jwharm/javagi/examples/notepad/Notepad.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.notepad; 2 | 3 | import org.gnome.gio.ApplicationFlags; 4 | import org.gnome.gtk.Application; 5 | 6 | /** 7 | * This is the Notepad application class. It contains the main method that will 8 | * create and run the application. 9 | */ 10 | public class Notepad extends Application { 11 | 12 | public static void main(String[] args) { 13 | var app = new Notepad(); 14 | app.run(args); 15 | } 16 | 17 | // Constructor 18 | public Notepad() { 19 | setApplicationId("my.example.Notepad"); 20 | setFlags(ApplicationFlags.DEFAULT_FLAGS); 21 | } 22 | 23 | /** 24 | * When the application is activated, create a new EditorWindow 25 | */ 26 | @Override 27 | public void activate() { 28 | var win = this.getActiveWindow(); 29 | if (win == null) { 30 | win = new EditorWindow(this); 31 | win.setDefaultSize(600, 400); 32 | } 33 | win.present(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /OpenGL/README.md: -------------------------------------------------------------------------------- 1 | ## OpenGL 2 | 3 | This example demonstrates an integration of 3D view in a Gtk application. 4 | 5 | It uses: 6 | * [LWJGL](https://www.lwjgl.org/) for OpenGL rendering, supporting both GL & GLES APIs 7 | * [JOML](https://github.com/JOML-CI/JOML) to handle maths 8 | 9 | To run the example, clone the repository, navigate to the `OpenGL` folder, and execute `gradle run`. 10 | 11 | ![OpenGL screenshot](opengl.png) 12 | -------------------------------------------------------------------------------- /OpenGL/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.21" 3 | application 4 | } 5 | 6 | group = "io.jwharm.javagi.examples" 7 | version = "1.0-SNAPSHOT" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation(kotlin("stdlib")) 15 | implementation("io.github.jwharm.javagi:gtk:0.11.2") 16 | implementation("io.github.jwharm.javagi:adw:0.11.2") 17 | implementation("org.lwjgl:lwjgl:3.3.6") 18 | runtimeOnly("org.lwjgl:lwjgl:3.3.6:natives-linux") 19 | implementation("org.lwjgl:lwjgl-opengl:3.3.6") 20 | runtimeOnly("org.lwjgl:lwjgl-opengl:3.3.6:natives-linux") 21 | runtimeOnly("org.lwjgl:lwjgl-opengl:3.3.6:natives-windows") 22 | runtimeOnly("org.lwjgl:lwjgl-opengl:3.3.6:natives-macos") 23 | implementation("org.lwjgl:lwjgl-opengles:3.3.6") 24 | runtimeOnly("org.lwjgl:lwjgl-opengles:3.3.6:natives-linux") 25 | runtimeOnly("org.lwjgl:lwjgl-opengles:3.3.6:natives-windows") 26 | runtimeOnly("org.lwjgl:lwjgl-opengles:3.3.6:natives-macos") 27 | implementation("org.joml:joml:1.10.8") 28 | } 29 | 30 | tasks.named("run") { 31 | jvmArgs("--enable-native-access=ALL-UNNAMED") 32 | } 33 | 34 | application { 35 | mainClass.set("io.jwharm.javagi.examples.MainKt") 36 | } 37 | 38 | kotlin { 39 | jvmToolchain(22) 40 | } -------------------------------------------------------------------------------- /OpenGL/opengl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/OpenGL/opengl.png -------------------------------------------------------------------------------- /OpenGL/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "OpenGL" 2 | -------------------------------------------------------------------------------- /OpenGL/src/main/kotlin/io/jwharm/javagi/examples/Main.kt: -------------------------------------------------------------------------------- 1 | package io.jwharm.javagi.examples 2 | 3 | import io.jwharm.javagi.examples.renderers.GLESRenderer 4 | import io.jwharm.javagi.examples.renderers.GLRenderer 5 | import io.jwharm.javagi.examples.renderers.Renderer 6 | import org.gnome.adw.Application 7 | import org.gnome.adw.ApplicationWindow 8 | import org.gnome.adw.ToolbarView 9 | import org.gnome.gdk.GLAPI 10 | import org.gnome.gio.ApplicationFlags 11 | import org.gnome.gtk.* 12 | 13 | fun main(args: Array) { 14 | App().run(args) 15 | } 16 | 17 | class App: Application("org.gnome.gtk.demo.opengl", ApplicationFlags.DEFAULT_FLAGS) { 18 | init { 19 | onActivate { 20 | Window(this).present() 21 | } 22 | } 23 | } 24 | 25 | class Window( 26 | application: Application, 27 | ): ApplicationWindow(application) { 28 | 29 | private lateinit var renderer: Renderer 30 | private var tickCallback: Int = -1 31 | 32 | init { 33 | title = "OpenGL" 34 | setDefaultSize(250, 300) 35 | content = ToolbarView().apply { 36 | addTopBar(HeaderBar()) 37 | content = GLArea().apply { setup() } 38 | } 39 | } 40 | 41 | private fun GLArea.setup() { 42 | hexpand = true 43 | vexpand = true 44 | setSizeRequest(500, 500) 45 | // You could force the use of a certain API as follows (if it's supported) 46 | // allowedApis = mutableSetOf(GLAPI.GL) 47 | // allowedApis = mutableSetOf(GLAPI.GLES) 48 | 49 | onRealize { 50 | makeCurrent() 51 | println("Graphics API: $api") 52 | 53 | // select a renderer for the current API 54 | renderer = when (api.first()) { 55 | GLAPI.GL -> GLRenderer() 56 | GLAPI.GLES -> GLESRenderer() 57 | } 58 | 59 | renderer.onInit() 60 | 61 | // for continuous rendering 62 | tickCallback = addTickCallback { _, _ -> 63 | queueRender() 64 | true 65 | } 66 | } 67 | 68 | onUnrealize { 69 | removeTickCallback(tickCallback) 70 | 71 | renderer.onDestroy() 72 | } 73 | 74 | onResize { width, height -> renderer.onResize(width, height) } 75 | onRender { context -> renderer.onRender(context) } 76 | } 77 | } -------------------------------------------------------------------------------- /OpenGL/src/main/kotlin/io/jwharm/javagi/examples/renderers/GLESRenderer.kt: -------------------------------------------------------------------------------- 1 | package io.jwharm.javagi.examples.renderers 2 | 3 | import org.gnome.gdk.GLContext 4 | import org.joml.Math.toRadians 5 | import org.joml.Matrix4f 6 | import org.lwjgl.opengles.GLES 7 | import org.lwjgl.opengles.GLES30.GL_COLOR_BUFFER_BIT 8 | import org.lwjgl.opengles.GLES30.GL_FALSE 9 | import org.lwjgl.opengles.GLES30.GL_FLOAT 10 | import org.lwjgl.opengles.GLES30.GL_TRIANGLES 11 | import org.lwjgl.opengles.GLES30.glClear 12 | import org.lwjgl.opengles.GLES30.glClearColor 13 | import org.lwjgl.opengles.GLES30.glDrawArrays 14 | import org.lwjgl.opengles.GLES30.GL_ARRAY_BUFFER 15 | import org.lwjgl.opengles.GLES30.GL_STATIC_DRAW 16 | import org.lwjgl.opengles.GLES30.glBindBuffer 17 | import org.lwjgl.opengles.GLES30.glBufferData 18 | import org.lwjgl.opengles.GLES30.glDeleteBuffers 19 | import org.lwjgl.opengles.GLES30.glGenBuffers 20 | import org.lwjgl.opengles.GLES30.GL_COMPILE_STATUS 21 | import org.lwjgl.opengles.GLES30.GL_FRAGMENT_SHADER 22 | import org.lwjgl.opengles.GLES30.GL_VERTEX_SHADER 23 | import org.lwjgl.opengles.GLES30.glAttachShader 24 | import org.lwjgl.opengles.GLES30.glCompileShader 25 | import org.lwjgl.opengles.GLES30.glCreateProgram 26 | import org.lwjgl.opengles.GLES30.glCreateShader 27 | import org.lwjgl.opengles.GLES30.glDeleteProgram 28 | import org.lwjgl.opengles.GLES30.glEnableVertexAttribArray 29 | import org.lwjgl.opengles.GLES30.glGetShaderInfoLog 30 | import org.lwjgl.opengles.GLES30.glGetShaderi 31 | import org.lwjgl.opengles.GLES30.glGetUniformLocation 32 | import org.lwjgl.opengles.GLES30.glLinkProgram 33 | import org.lwjgl.opengles.GLES30.glShaderSource 34 | import org.lwjgl.opengles.GLES30.glUniformMatrix4fv 35 | import org.lwjgl.opengles.GLES30.glUseProgram 36 | import org.lwjgl.opengles.GLES30.glVertexAttribPointer 37 | import org.lwjgl.opengles.GLES30.glBindVertexArray 38 | import org.lwjgl.opengles.GLES30.glDeleteVertexArrays 39 | import org.lwjgl.opengles.GLES30.glGenVertexArrays 40 | 41 | private val vertexShaderSource = """ 42 | #version 300 es 43 | layout (location = 0) in vec3 aPos; 44 | uniform mat4 model; 45 | uniform mat4 projection; 46 | void main() { 47 | gl_Position = projection * model * vec4(aPos.x, aPos.y, aPos.z, 1.0); 48 | } 49 | """.trimIndent() 50 | private val fragmentShaderSource = """ 51 | #version 300 es 52 | precision mediump float; 53 | out vec4 FragColor; 54 | void main() { 55 | FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Rouge 56 | } 57 | """.trimIndent() 58 | 59 | class GLESRenderer : Renderer { 60 | 61 | private var vaoId = -1 62 | private var vboId = -1 63 | private var shaderProgramId = -1 64 | private var modelMatrixUniformLocation = -1 65 | private val modelMatrix = Matrix4f().identity() 66 | private var projectionMatrixUniformLocation = -1 67 | private val projectionMatrix = Matrix4f() 68 | 69 | override fun onInit() { 70 | GLES.createCapabilities() 71 | 72 | // create vertex array object 73 | vaoId = glGenVertexArrays() 74 | glBindVertexArray(vaoId) 75 | 76 | // create vertex buffer object 77 | vboId = glGenBuffers() 78 | glBindBuffer(GL_ARRAY_BUFFER, vboId) 79 | glBufferData(GL_ARRAY_BUFFER, triangleVertices, GL_STATIC_DRAW) 80 | 81 | // create and compile shaders 82 | val vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSource) 83 | val fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSource) 84 | 85 | // create shader program 86 | shaderProgramId = glCreateProgram() 87 | glAttachShader(shaderProgramId, vertexShader!!) 88 | glAttachShader(shaderProgramId, fragmentShader!!) 89 | glLinkProgram(shaderProgramId) 90 | 91 | // configure vertices 92 | glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0) 93 | glEnableVertexAttribArray(0) 94 | 95 | // get uniform locations 96 | modelMatrixUniformLocation = glGetUniformLocation(shaderProgramId, "model") 97 | projectionMatrixUniformLocation = glGetUniformLocation(shaderProgramId, "projection") 98 | 99 | // set background color 100 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f) 101 | } 102 | 103 | override fun onResize(width: Int, height: Int) { 104 | // preserve aspect ratio on resize 105 | val aspectRatio = width.toFloat() / height.toFloat() 106 | projectionMatrix.identity() 107 | if (width > height) { 108 | projectionMatrix.ortho(-aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f) 109 | } else { 110 | projectionMatrix.ortho(-1.0f, 1.0f, -1.0f / aspectRatio, 1.0f / aspectRatio, -1.0f, 1.0f) 111 | } 112 | 113 | glUseProgram(shaderProgramId) 114 | glUniformMatrix4fv(projectionMatrixUniformLocation, false, projectionMatrix.get(FloatArray(16))) 115 | } 116 | 117 | override fun onRender(context: GLContext): Boolean { 118 | glClear(GL_COLOR_BUFFER_BIT) 119 | 120 | // draw triangle 121 | glUseProgram(shaderProgramId) 122 | 123 | modelMatrix.rotateZ(toRadians(1.0).toFloat()) 124 | glUniformMatrix4fv(modelMatrixUniformLocation, false, modelMatrix.get(FloatArray(16))) 125 | 126 | glBindVertexArray(vaoId) 127 | glDrawArrays(GL_TRIANGLES, 0, 3) 128 | 129 | return true 130 | } 131 | 132 | override fun onDestroy() { 133 | // free resources 134 | glDeleteVertexArrays(vaoId) 135 | glDeleteBuffers(vboId) 136 | glDeleteProgram(shaderProgramId) 137 | } 138 | 139 | private fun createShader(type: Int, source: String): Int? { 140 | val shader = glCreateShader(type) 141 | glShaderSource(shader, source) 142 | glCompileShader(shader) 143 | 144 | if (glGetShaderi(shader, GL_COMPILE_STATUS) == GL_FALSE) { 145 | val infoLog = glGetShaderInfoLog(shader) 146 | println("Shader compilation error: $infoLog") 147 | return null 148 | } 149 | 150 | return shader 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /OpenGL/src/main/kotlin/io/jwharm/javagi/examples/renderers/GLRenderer.kt: -------------------------------------------------------------------------------- 1 | package io.jwharm.javagi.examples.renderers 2 | 3 | import org.gnome.gdk.GLContext 4 | import org.joml.Math.toRadians 5 | import org.joml.Matrix4f 6 | import org.lwjgl.opengl.GL 7 | import org.lwjgl.opengl.GL30.GL_COLOR_BUFFER_BIT 8 | import org.lwjgl.opengl.GL30.GL_FALSE 9 | import org.lwjgl.opengl.GL30.GL_FLOAT 10 | import org.lwjgl.opengl.GL30.GL_TRIANGLES 11 | import org.lwjgl.opengl.GL30.glClear 12 | import org.lwjgl.opengl.GL30.glClearColor 13 | import org.lwjgl.opengl.GL30.glDrawArrays 14 | import org.lwjgl.opengl.GL30.GL_ARRAY_BUFFER 15 | import org.lwjgl.opengl.GL30.GL_STATIC_DRAW 16 | import org.lwjgl.opengl.GL30.glBindBuffer 17 | import org.lwjgl.opengl.GL30.glBufferData 18 | import org.lwjgl.opengl.GL30.glDeleteBuffers 19 | import org.lwjgl.opengl.GL30.glGenBuffers 20 | import org.lwjgl.opengl.GL30.GL_COMPILE_STATUS 21 | import org.lwjgl.opengl.GL30.GL_FRAGMENT_SHADER 22 | import org.lwjgl.opengl.GL30.GL_VERTEX_SHADER 23 | import org.lwjgl.opengl.GL30.glAttachShader 24 | import org.lwjgl.opengl.GL30.glCompileShader 25 | import org.lwjgl.opengl.GL30.glCreateProgram 26 | import org.lwjgl.opengl.GL30.glCreateShader 27 | import org.lwjgl.opengl.GL30.glDeleteProgram 28 | import org.lwjgl.opengl.GL30.glEnableVertexAttribArray 29 | import org.lwjgl.opengl.GL30.glGetShaderInfoLog 30 | import org.lwjgl.opengl.GL30.glGetShaderi 31 | import org.lwjgl.opengl.GL30.glGetUniformLocation 32 | import org.lwjgl.opengl.GL30.glLinkProgram 33 | import org.lwjgl.opengl.GL30.glShaderSource 34 | import org.lwjgl.opengl.GL30.glUniformMatrix4fv 35 | import org.lwjgl.opengl.GL30.glUseProgram 36 | import org.lwjgl.opengl.GL30.glVertexAttribPointer 37 | import org.lwjgl.opengl.GL30.glBindVertexArray 38 | import org.lwjgl.opengl.GL30.glDeleteVertexArrays 39 | import org.lwjgl.opengl.GL30.glGenVertexArrays 40 | 41 | private val vertexShaderSource = """ 42 | #version 330 core 43 | layout (location = 0) in vec3 aPos; 44 | uniform mat4 model; 45 | uniform mat4 projection; 46 | void main() { 47 | gl_Position = projection * model * vec4(aPos.x, aPos.y, aPos.z, 1.0); 48 | } 49 | """.trimIndent() 50 | private val fragmentShaderSource = """ 51 | #version 330 core 52 | out vec4 FragColor; 53 | void main() { 54 | FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Rouge 55 | } 56 | """.trimIndent() 57 | 58 | class GLRenderer : Renderer { 59 | 60 | private var vaoId = -1 61 | private var vboId = -1 62 | private var shaderProgramId = -1 63 | private var modelMatrixUniformLocation = -1 64 | private val modelMatrix = Matrix4f().identity() 65 | private var projectionMatrixUniformLocation = -1 66 | private val projectionMatrix = Matrix4f() 67 | 68 | override fun onInit() { 69 | GL.createCapabilities() 70 | 71 | // create vertex array object 72 | vaoId = glGenVertexArrays() 73 | glBindVertexArray(vaoId) 74 | 75 | // create vertex buffer object 76 | vboId = glGenBuffers() 77 | glBindBuffer(GL_ARRAY_BUFFER, vboId) 78 | glBufferData(GL_ARRAY_BUFFER, triangleVertices, GL_STATIC_DRAW) 79 | 80 | // create and compile shaders 81 | val vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSource) 82 | val fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSource) 83 | 84 | // create shader program 85 | shaderProgramId = glCreateProgram() 86 | glAttachShader(shaderProgramId, vertexShader!!) 87 | glAttachShader(shaderProgramId, fragmentShader!!) 88 | glLinkProgram(shaderProgramId) 89 | 90 | // configure vertices 91 | glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0) 92 | glEnableVertexAttribArray(0) 93 | 94 | // get uniform locations 95 | modelMatrixUniformLocation = glGetUniformLocation(shaderProgramId, "model") 96 | projectionMatrixUniformLocation = glGetUniformLocation(shaderProgramId, "projection") 97 | 98 | // set background color 99 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f) 100 | } 101 | 102 | override fun onResize(width: Int, height: Int) { 103 | // preserve aspect ratio on resize 104 | val aspectRatio = width.toFloat() / height.toFloat() 105 | projectionMatrix.identity() 106 | if (width > height) { 107 | projectionMatrix.ortho(-aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f) 108 | } else { 109 | projectionMatrix.ortho(-1.0f, 1.0f, -1.0f / aspectRatio, 1.0f / aspectRatio, -1.0f, 1.0f) 110 | } 111 | 112 | glUseProgram(shaderProgramId) 113 | glUniformMatrix4fv(projectionMatrixUniformLocation, false, projectionMatrix.get(FloatArray(16))) 114 | } 115 | 116 | override fun onRender(context: GLContext): Boolean { 117 | glClear(GL_COLOR_BUFFER_BIT) 118 | 119 | // draw triangle 120 | glUseProgram(shaderProgramId) 121 | 122 | modelMatrix.rotateZ(toRadians(1.0).toFloat()) 123 | glUniformMatrix4fv(modelMatrixUniformLocation, false, modelMatrix.get(FloatArray(16))) 124 | 125 | glBindVertexArray(vaoId) 126 | glDrawArrays(GL_TRIANGLES, 0, 3) 127 | 128 | return true 129 | } 130 | 131 | override fun onDestroy() { 132 | // free resources 133 | glDeleteVertexArrays(vaoId) 134 | glDeleteBuffers(vboId) 135 | glDeleteProgram(shaderProgramId) 136 | } 137 | 138 | private fun createShader(type: Int, source: String): Int? { 139 | val shader = glCreateShader(type) 140 | glShaderSource(shader, source) 141 | glCompileShader(shader) 142 | 143 | if (glGetShaderi(shader, GL_COMPILE_STATUS) == GL_FALSE) { 144 | val infoLog = glGetShaderInfoLog(shader) 145 | println("Shader compilation error: $infoLog") 146 | return null 147 | } 148 | 149 | return shader 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /OpenGL/src/main/kotlin/io/jwharm/javagi/examples/renderers/Renderer.kt: -------------------------------------------------------------------------------- 1 | package io.jwharm.javagi.examples.renderers 2 | 3 | import org.gnome.gdk.GLContext 4 | 5 | val triangleVertices = floatArrayOf( 6 | -0.5f, -0.5f, 0.0f, 7 | 0.5f, -0.5f, 0.0f, 8 | 0.0f, 0.5f, 0.0f, 9 | ) 10 | 11 | interface Renderer { 12 | 13 | fun onInit() 14 | fun onResize(width: Int, height: Int) 15 | fun onRender(context: GLContext): Boolean 16 | fun onDestroy() 17 | 18 | } -------------------------------------------------------------------------------- /PegSolitaire/README.md: -------------------------------------------------------------------------------- 1 | ## Peg Solitaire 2 | 3 | This example is a direct port of the Peg-Solitaire example in the Gtk Demo. 4 | 5 | To run the example, clone the repository, navigate to the `PegSolitaire` folder, and execute `gradle run`. 6 | 7 | ![Peg Solitaire screenshot](peg-solitaire.png) 8 | -------------------------------------------------------------------------------- /PegSolitaire/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gtk:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += '--enable-native-access=ALL-UNNAMED' 21 | } 22 | 23 | application { 24 | mainClass = 'io.github.jwharm.javagi.examples.pegsolitaire.PegSolitaire' 25 | } 26 | 27 | -------------------------------------------------------------------------------- /PegSolitaire/peg-solitaire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/PegSolitaire/peg-solitaire.png -------------------------------------------------------------------------------- /PlaySound/README.md: -------------------------------------------------------------------------------- 1 | ## Play Sound 2 | 3 | This example is a simple "Hello World" GStreamer application. It was ported from [this example](https://gstreamer.freedesktop.org/documentation/application-development/basics/helloworld.html?gi-language=c) in the GStreamer Application Development Manual. 4 | 5 | The example sound file was downloaded from [freesound.org](https://freesound.org/people/ispeakwaves/sounds/455516/), where it was published by user `ispeakwaves`. The sound file is licensed under the Creative Commons Attribution 4.0 License ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)). 6 | 7 | To run the example, clone the repository, navigate to the `PlaySound` folder, and execute `gradle run`. 8 | -------------------------------------------------------------------------------- /PlaySound/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gst:0.12.2' 11 | } 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(22) 16 | } 17 | } 18 | 19 | tasks.named('run') { 20 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 21 | } 22 | 23 | application { 24 | mainClass = "io.github.jwharm.javagi.examples.playsound.PlaySound" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /PlaySound/src/main/java/io/github/jwharm/javagi/examples/playsound/PlaySound.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.playsound; 2 | 3 | import io.github.jwharm.javagi.base.Out; 4 | import org.freedesktop.gstreamer.gst.*; 5 | import org.gnome.glib.GError; 6 | import org.gnome.glib.GLib; 7 | import org.gnome.glib.MainLoop; 8 | import org.gnome.glib.Source; 9 | 10 | import java.util.Objects; 11 | import java.util.stream.Stream; 12 | 13 | public class PlaySound { 14 | 15 | private static final String FILENAME = "src/main/resources/example.ogg"; 16 | 17 | MainLoop loop; 18 | Pipeline pipeline; 19 | Element source, demuxer, decoder, conv, sink; 20 | Bus bus; 21 | int busWatchId; 22 | 23 | private boolean busCall(Bus bus, Message msg) { 24 | 25 | if (msg.readType().contains(MessageType.EOS)) { 26 | GLib.print("End of stream\n"); 27 | loop.quit(); 28 | } 29 | 30 | else if (msg.readType().contains(MessageType.ERROR)) { 31 | Out error = new Out<>(); 32 | Out debug = new Out<>(); 33 | msg.parseError(error, debug); 34 | 35 | GLib.printerr("Error: %s\n", error.get().readMessage()); 36 | 37 | loop.quit(); 38 | } 39 | 40 | return true; 41 | } 42 | 43 | private void onPadAdded(Pad pad) { 44 | 45 | // We can now link this pad with the vorbis-decoder sink pad 46 | GLib.print("Dynamic pad created, linking demuxer/decoder\n"); 47 | 48 | Pad sinkpad = decoder.getStaticPad("sink"); 49 | 50 | if (sinkpad != null) { 51 | pad.link(sinkpad); 52 | } else { 53 | GLib.printerr("Sink pad not set!\n"); 54 | } 55 | } 56 | 57 | public PlaySound(String[] args) { 58 | 59 | // Initialisation 60 | Gst.init(new Out<>(args)); 61 | 62 | loop = new MainLoop(null, false); 63 | 64 | // Create gstreamer elements 65 | pipeline = new Pipeline("audio-player"); 66 | source = ElementFactory.make("filesrc", "file-source"); 67 | demuxer = ElementFactory.make("oggdemux", "ogg-demuxer"); 68 | decoder = ElementFactory.make("vorbisdec", "vorbis-decoder"); 69 | conv = ElementFactory.make("audioconvert", "converter"); 70 | sink = ElementFactory.make("autoaudiosink", "audio-output"); 71 | 72 | if (Stream.of(source, demuxer, decoder, conv, sink).anyMatch(Objects::isNull)) { 73 | GLib.printerr("One element could not be created. Exiting.\n"); 74 | return; 75 | } 76 | 77 | // Set up the pipeline 78 | 79 | // We set the input filename to the source element 80 | source.set("location", FILENAME, null); 81 | 82 | // We add a message handler 83 | bus = pipeline.getBus(); 84 | busWatchId = bus.addWatch(0, this::busCall); 85 | 86 | // We add all elements into the pipeline 87 | // file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output 88 | pipeline.addMany(source, demuxer, decoder, conv, sink, null); 89 | 90 | // We link the elements together 91 | // file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output 92 | source.link(demuxer); 93 | decoder.linkMany(conv, sink, null); 94 | demuxer.onPadAdded(this::onPadAdded); 95 | 96 | // Set the pipeline 97 | GLib.print("Now playing: %s\n", FILENAME); 98 | pipeline.setState(State.PLAYING); 99 | 100 | // Iterate 101 | GLib.print("Running...\n"); 102 | loop.run(); 103 | 104 | // Out of the main loop, clean up nicely 105 | GLib.print("Returned, stopping playback\n"); 106 | pipeline.setState(State.NULL); 107 | 108 | GLib.print("Deleting pipeline\n"); 109 | Source.remove(busWatchId); 110 | } 111 | 112 | public static void main(String[] args) { 113 | new PlaySound(args); 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /PlaySound/src/main/resources/example.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/PlaySound/src/main/resources/example.ogg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Examples for Java-GI 2 | 3 | This repository contains small example applications made with [Java-GI](https://github.com/jwharm/java-gi). Each example can be separately built and run with `gradle run`: 4 | 5 | ### Hello World 6 | 7 | A typical ["Hello World" example](https://github.com/jwharm/java-gi-examples/tree/main/HelloWorld) that displays a Gtk window with a button. When you click the button, the application quits. 8 | 9 | ### Hello World Scala 10 | 11 | THe same as the above but written in Scala. 12 | 13 | ### Hello World - template based 14 | 15 | This is a bit more complete ["Hello World" example](https://github.com/jwharm/java-gi-examples/tree/main/HelloTemplate) that is based on the default application that GNOME Builder generates for new Vala projects. It uses Gtk composite template classes to define the user interface in XML files. It also includes a Flatpak manifest and instructions to install and run the example as a Flatpak application. 16 | 17 | ### Calculator 18 | 19 | A [basic calculator](https://github.com/jwharm/java-gi-examples/tree/main/Calculator) that uses Gtk and LibAdwaita. There's a header bar, and a grid-based layout for the buttons. The app reacts to key presses as expected. 20 | 21 | ### ColumnView Datagrid 22 | 23 | An [example GtkColumnView](https://github.com/jwharm/java-gi-examples/tree/main/ColumnViewDatagrid) displaying a long grid of items. 24 | 25 | ### List integration 26 | 27 | This example [demonstrates](https://github.com/jwharm/java-gi-examples/tree/main/ListViewer) how you can use a Java ArrayList to implement the GListModel interface, which is central to all modern Gtk list widgets. 28 | 29 | ### Notepad 30 | 31 | A very basic Adwaita [plain-text editor](https://github.com/jwharm/java-gi-examples/tree/main/Notepad), that can load and save files using GIO. 32 | 33 | ### Image Viewer 34 | 35 | A basic Adwaita [image viewer](https://github.com/jwharm/java-gi-examples/tree/main/ImageViewer) written in Scala with a clickable button that opens a file dialog. 36 | 37 | ### Code editor 38 | 39 | A [source code editor](https://github.com/jwharm/java-gi-examples/tree/main/CodeEditor). It is mostly the same application as the Notepad example above, but this one uses GtkSourceview as the text widget and enables line numbers and syntax highlighting. 40 | 41 | ### Peg Solitaire 42 | 43 | This [game](https://github.com/jwharm/java-gi-examples/tree/main/PegSolitaire) is a direct port of the Peg-Solitaire drag-and-drop example in the Gtk Demo. 44 | 45 | ### WebkitGtk Web browser 46 | 47 | This example creates a very basic [web browser](https://github.com/jwharm/java-gi-examples/tree/main/Browser) using WebkitGtk. It was inspired by the browser example in GNOME Workbench. 48 | 49 | ### WebkitGtk Javascript callback 50 | 51 | This example is a [small WebkitGtk application](https://github.com/jwharm/java-gi-examples/tree/main/Javascript) that runs a Java callback function from a Javascript function in the webpage. 52 | 53 | ### Mediastream 54 | 55 | The [Mediastream example](https://github.com/jwharm/java-gi-examples/tree/main/MediaStream) is ported from GtkDemo. It paints a simple image using Cairo, and then rotates the image in a GtkVideo widget. The Cairo draw commands use the [Cairo Java bindings](https://github.com/jwharm/cairo-java-bindings). 56 | 57 | ### GStreamer sound player 58 | 59 | This example [demonstrates how to use GStreamer](https://github.com/jwharm/java-gi-examples/tree/main/PlaySound) and is ported from the GStreamer tutorials. It creates a GStreamer pipeline that will play sound from an Ogg Vorbis file. 60 | 61 | ### GStreamer screen recorder 62 | 63 | A command-line [screen recording](https://github.com/jwharm/java-gi-examples/tree/main/ScreenRecorder) utility that records the screen for 5 seconds into an Ogg/Theora file. It also demonstrates how to add a Tee and an AppSink to the pipeline so you can trigger custom actions on the recorded data. 64 | 65 | ### Logging adapter 66 | 67 | [A small experiment](https://github.com/jwharm/java-gi-examples/tree/main/Logging) to redirect the output of the GLib logging functions to the Java logging framework SLF4J. 68 | 69 | ### Spacewar 70 | 71 | The classic [Spacewar game](https://github.com/jwharm/java-gi-examples/tree/main/Spacewar), ported from the cairo SVG Spacewar demo to Gtk4 and Kotlin, using Java-GI. 72 | 73 | ### OpenGL 74 | 75 | An [OpenGL](https://github.com/jwharm/java-gi-examples/tree/main/OpenGL) demo demonstrating how to achive 3D rendering using Gtk.GLArea together with LWJGL. 76 | -------------------------------------------------------------------------------- /ScreenRecorder/README.md: -------------------------------------------------------------------------------- 1 | ## Screen Recorder 2 | 3 | This example is a basic X11 screen recorder using GStreamer using the `ximagesrc` plugin and ogg/theora output. It also implements a Tee that streams data to an AppSink, to demonstrate performing custom actions on the buffer data. 4 | 5 | To run the example, clone the repository, navigate to the `ScreenRecorder` folder, and execute `gradle run`. 6 | 7 | -------------------------------------------------------------------------------- /ScreenRecorder/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation 'io.github.jwharm.javagi:gst:0.12.2' 11 | implementation 'io.github.jwharm.javagi:gstbase:0.12.2' 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion = JavaLanguageVersion.of(22) 17 | } 18 | } 19 | 20 | tasks.named('run') { 21 | jvmArgs += "--enable-native-access=ALL-UNNAMED" 22 | } 23 | 24 | application { 25 | mainClass = "io.github.jwharm.javagi.examples.screenrec.ScreenRecorder" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ScreenRecorder/src/main/java/io/github/jwharm/javagi/examples/screenrec/ScreenRecorder.java: -------------------------------------------------------------------------------- 1 | package io.github.jwharm.javagi.examples.screenrec; 2 | 3 | import io.github.jwharm.javagi.base.Out; 4 | import io.github.jwharm.javagi.gobject.JavaClosure; 5 | import org.freedesktop.gstreamer.base.BaseSink; 6 | import org.freedesktop.gstreamer.gst.*; 7 | import org.gnome.glib.GError; 8 | import org.gnome.glib.GLib; 9 | import org.gnome.glib.MainLoop; 10 | import org.gnome.glib.Source; 11 | import org.gnome.gobject.GObjects; 12 | 13 | /** 14 | * Record the screen and save it to a file. 15 | * The recording will automatically stop after 5 seconds. 16 | *

17 | * There is also an AppSink configured, that receives the stream data 18 | * and can perform custom actions on it. 19 | */ 20 | public class ScreenRecorder { 21 | 22 | private final static String FILENAME = "recording.ogg"; 23 | private final static int TIMEOUT = 5 * 1000; // milliseconds 24 | 25 | private final MainLoop loop; 26 | 27 | private boolean busCall(Bus bus, Message msg) { 28 | 29 | if (msg.readType().contains(MessageType.EOS)) { 30 | GLib.print("End of stream\n"); 31 | loop.quit(); 32 | } 33 | 34 | else if (msg.readType().contains(MessageType.ERROR)) { 35 | Out error = new Out<>(); 36 | Out debug = new Out<>(); 37 | msg.parseError(error, debug); 38 | 39 | GLib.printerr("Error: %s\n", error.get().readMessage()); 40 | 41 | loop.quit(); 42 | } 43 | 44 | return true; 45 | } 46 | 47 | /** 48 | * This callback function is triggered when the appsink receives a data sample. 49 | */ 50 | public void newSample(Element sink) { 51 | 52 | /* 53 | * GStreamer plugins aren't registered as introspectable types: In this specific 54 | * case, we need the GstApp-1.0.gir - but it isn't in the gir-files repository 55 | * currently. As a result, Java doesn't recognize the AppSink type, which is 56 | * derived from BaseSink (which is a known type), and as a fallback, it considers 57 | * the appsink to be an Element. 58 | * Trying to cast the Element to BaseSink will throw a ClassCastException, so 59 | * that is not an option. As a workaround, we manually construct a BaseSink from 60 | * the memory address of the Element. 61 | */ 62 | var appsink = new BaseSink.BaseSinkImpl(sink.handle()); 63 | 64 | // Get the buffer 65 | var sample = appsink.getLastSample(); 66 | if (sample != null) { 67 | var buffer = sample.getBuffer(); 68 | // We don't do anything with the data here - just print an asterisk for every sample 69 | GLib.print("*"); 70 | } 71 | } 72 | 73 | public ScreenRecorder(String[] args) { 74 | 75 | // Initialisation 76 | Gst.init(new Out<>(args)); 77 | 78 | loop = new MainLoop(null, false); 79 | 80 | // Create gstreamer elements 81 | Pipeline pipeline = new Pipeline("screen-recorder"); 82 | Element source = ElementFactory.make("ximagesrc", "ximage-source"); 83 | Element conv = ElementFactory.make("videoconvert", "video-converter"); 84 | Element tee = ElementFactory.make("tee", "tee"); 85 | Element queue1 = ElementFactory.make("queue", "queue1"); 86 | Element appsink = ElementFactory.make("appsink", "appsink"); 87 | Element queue2 = ElementFactory.make("queue", "queue2"); 88 | Element encoder = ElementFactory.make("theoraenc", "theora-encoder"); 89 | Element muxer = ElementFactory.make("oggmux", "ogg-muxer"); 90 | Element filesink = ElementFactory.make("filesink", "file-sink"); 91 | 92 | if (source == null || tee == null || queue1 == null || appsink == null || queue2 == null 93 | || muxer == null || encoder == null || conv == null || filesink == null) { 94 | GLib.printerr("One element could not be created. Exiting.\n"); 95 | return; 96 | } 97 | 98 | // Set the output filename to the filesink element 99 | filesink.set("location", FILENAME, null); 100 | 101 | // Watch the message bus for messages 102 | Bus bus = pipeline.getBus(); 103 | if (bus == null) { 104 | GLib.printerr("Cannot get the bus of the pipeline. Exiting.\n"); 105 | return; 106 | } 107 | int busWatchId = bus.addWatch(0, this::busCall); 108 | 109 | // Let the appsink invoke the newSample() method (declare above) 110 | appsink.set("emit-signals", true, null); 111 | JavaClosure closure = new JavaClosure(() -> newSample(appsink)); 112 | GObjects.signalConnectClosure(appsink, "new-sample", closure, false); 113 | 114 | // Add all elements into the pipeline 115 | pipeline.addMany(source, conv, tee, queue1, appsink, queue2, encoder, muxer, filesink, null); 116 | 117 | /* Link the pads: 118 | * / queue1 | appsink 119 | * ximagesrc | video/x-raw,framerate=5/1 | converter | tee 120 | * \ queue2 | encoder | muxer | file-output 121 | */ 122 | source.linkFiltered(conv, Caps.simple("video/x-raw", "framerate", Fraction.getType(), 5, 1, null)); 123 | conv.link(tee); 124 | tee.requestPadSimple("src_%u").link(queue1.getStaticPad("sink")); 125 | tee.requestPadSimple("src_%u").link(queue2.getStaticPad("sink")); 126 | queue1.link(appsink); 127 | queue2.linkMany(encoder, muxer, filesink, null); 128 | 129 | // Set the pipeline state 130 | GLib.print("Now recording to file: %s\n", FILENAME); 131 | pipeline.setState(State.PLAYING); 132 | 133 | // Stop after 5 seconds 134 | GLib.timeoutAddOnce(TIMEOUT, loop::quit); 135 | 136 | // Iterate 137 | GLib.print("Running...\n"); 138 | loop.run(); 139 | 140 | // Out of the main loop, clean up nicely 141 | GLib.print("Returned, stopping recording\n"); 142 | pipeline.setState(State.NULL); 143 | Source.remove(busWatchId); 144 | } 145 | 146 | public static void main(String[] args) { 147 | new ScreenRecorder(args); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Spacewar/README.md: -------------------------------------------------------------------------------- 1 | ## Spacewar 2 | 3 | This example is a port of the cairo "SVG Spacewar" demo to GTK 4 and Kotlin. 4 | The original can be found [here](https://gitlab.com/cairo/cairo-demos). It is 5 | mostly identical to the original, but uses a `GdkPaintable` for drawing. 6 | 7 | ### Controls 8 | 9 | The blue spaceship is controlled with a/w/d and left control, the right one 10 | with cursor keys and right control. Zoom in and out with left and right 11 | brackets. Quit the game with Escape. 12 | 13 | ### License 14 | 15 | Both the original demo and this program are licensed under the GNU GPL. 16 | 17 | ![Spacewar screenshot](spacewar.png) 18 | -------------------------------------------------------------------------------- /Spacewar/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.0" 3 | application 4 | } 5 | 6 | group = "io.jwharm.javagi.examples" 7 | version = "1.0-SNAPSHOT" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation("io.github.jwharm.cairobindings:cairo:1.18.4.1") 15 | implementation("io.github.jwharm.javagi:gtk:0.12.2") 16 | } 17 | 18 | kotlin { 19 | jvmToolchain(22) 20 | } 21 | 22 | application { 23 | applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED") 24 | mainClass = "io.github.jwharm.javagi.examples.SpacewarKt" 25 | } 26 | -------------------------------------------------------------------------------- /Spacewar/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 3 | } 4 | 5 | rootProject.name = "Spacewar" 6 | -------------------------------------------------------------------------------- /Spacewar/spacewar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwharm/java-gi-examples/dc94d0ddd84df624856f664fb575d3b1c30d080e/Spacewar/spacewar.png --------------------------------------------------------------------------------