diff --git a/CHANGELOG.md b/CHANGELOG.md index 73cbf7e4..8e96d0d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Next + +- Library: Fix Value size (ValueFactory) and property access +- Generator: Add cairo PNG support +- Examples: ImageIO sample + # 0.6.0 - Generator: Add markdown summary logs diff --git a/examples/src/main/java/examples/App.java b/examples/src/main/java/examples/App.java index 519fb8af..077080d3 100644 --- a/examples/src/main/java/examples/App.java +++ b/examples/src/main/java/examples/App.java @@ -28,6 +28,7 @@ import examples.gtk4_tutorial.CustomDrawing; import examples.gtk4_tutorial.ExampleApplication; import examples.gtk4_tutorial.UiBuilderExample; +import examples.image_io.ImageIo; public class App { @@ -71,6 +72,7 @@ public static void main (String[] args) { addSample(demoList, window, new ExampleApplication(app)); addSample(demoList, window, new GlibSettings()); addSample(demoList, window, new Accordion()); + addSample(demoList, window, new ImageIo()); window.present(); }); diff --git a/examples/src/main/java/examples/ImageBridge.java b/examples/src/main/java/examples/ImageBridge.java index c13568f8..8963fc6b 100644 --- a/examples/src/main/java/examples/ImageBridge.java +++ b/examples/src/main/java/examples/ImageBridge.java @@ -90,14 +90,12 @@ private Pixbuf loadPixbuf(int width, int height) throws IOException { } } - private boolean drawLogo(Context context) { + private void drawLogo(Context context) { if (pixbuf != null) { context.save(); Gdk.cairoSetSourcePixbuf(context, pixbuf, 0, 0); context.paint(); - return true; } - return false; } @Override diff --git a/examples/src/main/java/examples/gtk4_demo/Pixbufs.java b/examples/src/main/java/examples/gtk4_demo/Pixbufs.java index 2a751352..1070babe 100644 --- a/examples/src/main/java/examples/gtk4_demo/Pixbufs.java +++ b/examples/src/main/java/examples/gtk4_demo/Pixbufs.java @@ -57,7 +57,7 @@ public class Pixbufs implements DemoInterface { private final Pixbuf[] images = new Pixbuf[IMAGE_NAMES.length]; /* Widgets */ - private DrawingArea da; + private DrawingArea drawingArea; private static final long CYCLE_TIME = 3000000; /* 3 seconds */ private long startTime; @@ -163,7 +163,7 @@ private boolean onTick(FrameClock frameClock) { overallAlpha); } } - da.queueDraw(); + drawingArea.queueDraw(); return GlibConstants.SOURCE_CONTINUE; } @@ -175,11 +175,11 @@ public Window runDemo() { try { loadPixbufs(); frame = new Pixbuf(Colorspace.RGB, false, 8, backWidth, backHeight); - da = new DrawingArea(); + drawingArea = new DrawingArea(); - da.setDrawFunc((cb, drawingArea, cr, width, height, userData) -> onDraw(cr), null, (cb, data) -> {}); - demoWindow.setChild(da); + drawingArea.setDrawFunc((cb, drawingArea, cr, width, height, userData) -> onDraw(cr), null, (cb, data) -> {}); + demoWindow.setChild(drawingArea); demoWindow.setSizeRequest(backWidth, backHeight); demoWindow.addTickCallback((cb, widget, frame_clock, user_data) -> onTick(frame_clock), null, (cb, data) -> {}); @@ -193,7 +193,7 @@ public Window runDemo() { new Str("Failed to load an image: " + e.getMessage())); dialog.onResponse(response_id -> demoWindow.destroy()); - dialog.show(); + dialog.present(); } return demoWindow; } diff --git a/examples/src/main/java/examples/image_io/ImageIo.java b/examples/src/main/java/examples/image_io/ImageIo.java new file mode 100644 index 00000000..3b2c2743 --- /dev/null +++ b/examples/src/main/java/examples/image_io/ImageIo.java @@ -0,0 +1,227 @@ +package examples.image_io; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.bailu.gtk.cairo.Cairo; +import ch.bailu.gtk.cairo.Format; +import ch.bailu.gtk.cairo.Surface; +import ch.bailu.gtk.gdk.Gdk; +import ch.bailu.gtk.gdkpixbuf.Pixbuf; +import ch.bailu.gtk.gio.File; +import ch.bailu.gtk.gtk.Box; +import ch.bailu.gtk.gtk.Button; +import ch.bailu.gtk.gtk.DrawingArea; +import ch.bailu.gtk.gtk.DropDown; +import ch.bailu.gtk.gtk.FileDialog; +import ch.bailu.gtk.gtk.Label; +import ch.bailu.gtk.gtk.Orientation; +import ch.bailu.gtk.gtk.Widget; +import ch.bailu.gtk.gtk.Window; +import ch.bailu.gtk.lib.bridge.Image; +import ch.bailu.gtk.lib.util.JavaResource; +import ch.bailu.gtk.type.Str; +import ch.bailu.gtk.type.Strs; +import ch.bailu.gtk.type.exception.AllocationError; +import examples.DemoInterface; + +public class ImageIo implements DemoInterface { + private static int SIZE = 500; + private static Str TITLE = new Str("Image IO"); + private static Str DESCRIPTION = new Str("Load and save images using different methods"); + + private DrawingArea drawingArea; + private Surface imageSurface; + private Label statusLabel; + private DropDown methodDropdown; + + @Override + public Window runDemo() { + statusLabel = new Label(TITLE); + statusLabel.setXalign(0); + + var vbox = new Box(Orientation.VERTICAL, 5); + var hbox = new Box(Orientation.HORIZONTAL, 5); + var demoWindow = new Window(); + + hbox.append(createOpenButton(demoWindow)); + hbox.append(createSaveButton(demoWindow)); + hbox.append(createLoadDefaultButton()); + hbox.append(new Label("Method:")); + hbox.append(createMethodDropDown()); + + vbox.append(hbox); + vbox.append(createDrawingArea()); + vbox.append(statusLabel); + vbox.setMarginTop(5); + vbox.setMarginBottom(5); + vbox.setMarginStart(5); + vbox.setMarginEnd(5); + + loadDefaultImage(); + + demoWindow.setSizeRequest(SIZE, SIZE); + demoWindow.setChild(vbox); + return demoWindow; + } + + private Widget createDrawingArea() { + drawingArea = new DrawingArea(); + drawingArea.setHexpand(true); + drawingArea.setVexpand(true); + + drawingArea.setDrawFunc((__self, drawing_area, cr, width, height, user_data) -> { + if (imageSurface != null) { + cr.save(); + cr.setSourceSurface(imageSurface, 0, 0); + cr.paint(); + } + }, null, null); + return drawingArea; + } + + private Widget createMethodDropDown() { + var strs = new Strs(new String[]{"Cairo PNG", "Image Bridge"}); + methodDropdown = DropDown.newFromStringsDropDown(strs); + return methodDropdown; + } + + private Widget createLoadDefaultButton() { + var result = new Button(); + result.setLabel("GTK.svg"); + result.onClicked(()-> loadDefaultImage()); + return result; + } + + private Widget createSaveButton(Window parent) { + var result = new Button(); + result.setLabel("Save…"); + result.onClicked(()->{ + var fileDialog = new FileDialog(); + fileDialog.save(parent, null, (__self, source_object, res, data) -> { + try { + saveImage(fileDialog.saveFinish(res)); + } catch (AllocationError e) { + statusLabel.setLabel(e.getMessage()); + } + }, null); + }); + return result; + } + + private Widget createOpenButton(Window parent) { + var result = new Button(); + result.setLabel("Open…"); + result.onClicked(()->{ + var fileDialog = new FileDialog(); + fileDialog.open(parent, null, (__self, source_object, res, data) -> { + try { + loadImage(fileDialog.openFinish(res)); + } catch (AllocationError e) { + statusLabel.setLabel(e.getMessage()); + } + }, null); + }); + return result; + } + + private void loadDefaultImage() { + var path = "/GTK.svg"; + + try (InputStream inputStream = new JavaResource(path).asStream()) { + var start = System.currentTimeMillis(); + + var pixbuf = Image.load(inputStream, SIZE, SIZE); + var surface = Cairo.imageSurfaceCreate(Format.ARGB32, SIZE, SIZE); + var context = surface.createContext(); + Gdk.cairoSetSourcePixbuf(context,pixbuf,0,0); + context.paint(); + pixbuf.unref(); + var delta = System.currentTimeMillis() - start; + + statusLabel.setLabel(delta + "ms: R " + path); + imageSurface = surface; + drawingArea.queueDraw(); + } catch (IOException e) { + statusLabel.setLabel(e.getMessage()); + } + } + + private void saveImage(File file) { + if (file != null && file.isNotNull()) { + var path = file.getPath(); + var start = System.currentTimeMillis(); + + if (methodDropdown.getSelected() == 0) { + imageSurface.writeToPng(path); + } else { + imageBridgeWrite(path.toString()); + } + + var delta = System.currentTimeMillis() - start; + statusLabel.setLabel(delta + "ms: W " + path); + path.destroy(); + } + } + + private void imageBridgeWrite(String path) { + try (OutputStream outputStream = new FileOutputStream(path)){ + Pixbuf pixbuf = Gdk.pixbufGetFromSurface(imageSurface, 0, 0, imageSurface.getWidth(), imageSurface.getHeight()); + Image.save(outputStream, pixbuf, "png"); + pixbuf.unref(); + } catch (IOException e) { + statusLabel.setText(e.getMessage()); + } + } + + private void loadImage(File file) { + if (file != null && file.isNotNull()) { + var path = file.getPath(); + + var start = System.currentTimeMillis(); + Surface surface; + + if (methodDropdown.getSelected() == 0) { + surface = Cairo.imageSurfaceCreateFromPng(path); + } else { + surface = imageBridgeRead(path.toString()); + } + + if (surface != null) { + var delta = System.currentTimeMillis() - start; + statusLabel.setLabel(delta + "ms: R " + path); + imageSurface = surface; + drawingArea.queueDraw(); + } + path.destroy(); + } + } + + private Surface imageBridgeRead(String path) { + try (InputStream inputStream = new FileInputStream(path)) { + var pixbuf = Image.load(inputStream); + var surface = Cairo.imageSurfaceCreate(Format.ARGB32, pixbuf.getWidth(), pixbuf.getHeight()); + var context = surface.createContext(); + Gdk.cairoSetSourcePixbuf(context,pixbuf,0,0); + context.paint(); + pixbuf.unref(); + return surface; + } catch (IOException e) { + statusLabel.setText(e.getMessage()); + } + return null; + } + + @Override + public Str getTitle() { + return TITLE; + } + + @Override + public Str getDescription() { + return DESCRIPTION; + } +} diff --git a/examples/src/main/java/examples/libadwaita/layout/SplitViewDemo.java b/examples/src/main/java/examples/libadwaita/layout/SplitViewDemo.java new file mode 100644 index 00000000..5995f670 --- /dev/null +++ b/examples/src/main/java/examples/libadwaita/layout/SplitViewDemo.java @@ -0,0 +1,124 @@ +package examples.libadwaita.layout; + +import ch.bailu.gtk.adw.Application; +import ch.bailu.gtk.adw.ApplicationWindow; +import ch.bailu.gtk.adw.Breakpoint; +import ch.bailu.gtk.adw.BreakpointBin; +import ch.bailu.gtk.adw.BreakpointCondition; +import ch.bailu.gtk.adw.BreakpointConditionLengthType; +import ch.bailu.gtk.adw.HeaderBar; +import ch.bailu.gtk.adw.NavigationPage; +import ch.bailu.gtk.adw.NavigationSplitView; +import ch.bailu.gtk.gtk.Box; +import ch.bailu.gtk.gtk.Button; +import ch.bailu.gtk.gtk.Label; +import ch.bailu.gtk.gtk.Orientation; +import ch.bailu.gtk.gtk.PositionType; +import ch.bailu.gtk.gtk.Unit; +import ch.bailu.gtk.type.Str; +import ch.bailu.gtk.type.Strs; + +public class SplitViewDemo { + + + + public static void main(String[] args) { + var app = new Application(new Str("org.example.SplitViewDemo"), 0); + + app.onActivate(() -> { + var headerBar = new HeaderBar(); + var applicationWindow = new ApplicationWindow(app); + applicationWindow.setContent(createNavigationSplitView(applicationWindow, headerBar)); + applicationWindow.present(); + headerBar.setShowEndTitleButtons(true); + }); + + app.run(0, Strs.NULL); + } + + private static NavigationSplitView createNavigationSplitView(ApplicationWindow applicationWindow, HeaderBar headerBar) { + var navigationSplitView = new NavigationSplitView(); + var toLevel2Button = Button.newWithLabelButton("Show level 2"); + var toLevel1Button = Button.newWithLabelButton("Show level 1"); + + + var breakpoint = new Breakpoint(BreakpointCondition.newLengthBreakpointCondition(BreakpointConditionLengthType.MAX_WIDTH, 400d, Unit.POINTS)); + breakpoint.onApply(() -> { + navigationSplitView.setCollapsed(true); + toLevel2Button.setVisible(true); + toLevel1Button.setVisible(true); + }); + breakpoint.onUnapply(() -> { + navigationSplitView.setCollapsed(false); + toLevel2Button.setVisible(false); + toLevel1Button.setVisible(false); + }); + applicationWindow.addBreakpoint(breakpoint); + + toLevel1Button.onClicked(()->navigationSplitView.setShowContent(false)); + toLevel2Button.onClicked(()->navigationSplitView.setShowContent(true)); + + navigationSplitView.setSidebar(createLeftPage(toLevel2Button, "Level 1", headerBar)); + navigationSplitView.setContent(createRightPage(toLevel1Button, "Level 2 & 3")); + return navigationSplitView; + } + + private static NavigationPage createRightPage(Button button, String title) { + + var breakpointBin = new BreakpointBin(); + var box = new Box(Orientation.VERTICAL, 0); + var navigationSplitView = new NavigationSplitView(); + var toLevel3Button = Button.newWithLabelButton("Show level 3"); + var toLevel2Button = Button.newWithLabelButton("Show level 2"); + + + var breakpoint = new Breakpoint(BreakpointCondition.newLengthBreakpointCondition(BreakpointConditionLengthType.MAX_WIDTH, 400d, Unit.POINTS)); + breakpoint.onApply(() -> { + navigationSplitView.setCollapsed(true); + toLevel3Button.setVisible(true); + toLevel2Button.setVisible(true); + }); + breakpoint.onUnapply(() -> { + navigationSplitView.setCollapsed(false); + toLevel3Button.setVisible(false); + toLevel2Button.setVisible(false); + }); + + var headerBar = new HeaderBar(); + System.out.println(headerBar.getDecorationLayout()); + box.append(headerBar); + + toLevel3Button.onClicked(()->navigationSplitView.setShowContent(true)); + toLevel2Button.onClicked(()->navigationSplitView.setShowContent(false)); + + breakpointBin.addBreakpoint(breakpoint); + breakpointBin.setIntProperty("width-request", 100); + breakpointBin.setIntProperty("height-request", 100); + navigationSplitView.setContent(createInnerPage(toLevel2Button, "Level 3")); + navigationSplitView.setSidebar(createInnerPage(toLevel3Button, "Level 2")); + + navigationSplitView.setSidebarPosition(PositionType.RIGHT); + + box.append(navigationSplitView); + box.append(button); + breakpointBin.setChild(box); + + return new NavigationPage(breakpointBin, title); + } + + private static NavigationPage createInnerPage(Button button, String title) { + var box = new Box(Orientation.VERTICAL, 0); + box.append(new Label(title)); + box.append(button); + return new NavigationPage(box, title); + } + + private static NavigationPage createLeftPage(Button button, String title, HeaderBar headerBar) { + var box = new Box(Orientation.VERTICAL, 0); + + box.append(headerBar); + box.append(new Label(title)); + box.append(button); + return new NavigationPage(box, title); + } +} diff --git a/generator/src/main/resources/cairo-custom.gir b/generator/src/main/resources/cairo-custom.gir index 1c97f7ca..93e0412f 100644 --- a/generator/src/main/resources/cairo-custom.gir +++ b/generator/src/main/resources/cairo-custom.gir @@ -743,6 +743,21 @@ + + + + + + + + + + + + + + + @@ -1728,6 +1743,18 @@ + + + + + + + + + + + + diff --git a/java-gtk/src/main/java/ch/bailu/gtk/gtk/ValueFactory.java b/java-gtk/src/main/java/ch/bailu/gtk/gtk/ValueFactory.java index 06f1fd7b..9a79ce55 100644 --- a/java-gtk/src/main/java/ch/bailu/gtk/gtk/ValueFactory.java +++ b/java-gtk/src/main/java/ch/bailu/gtk/gtk/ValueFactory.java @@ -7,8 +7,11 @@ import ch.bailu.gtk.type.Str; public class ValueFactory { + // TODO: This should come from SizeTable.kt + private static final int VALUE_SIZE = 24; + public static Value initValue(long typeID) { - return new Value(new Record(5).cast()).init(typeID); + return new Value(new Record(VALUE_SIZE).cast()).init(typeID); } public static Value toValue(Str str) { diff --git a/java-gtk/src/test/java/ch/bailu/gtk/TestPropertyAccess.java b/java-gtk/src/test/java/ch/bailu/gtk/TestPropertyAccess.java index eec380d0..07cfbeb1 100644 --- a/java-gtk/src/test/java/ch/bailu/gtk/TestPropertyAccess.java +++ b/java-gtk/src/test/java/ch/bailu/gtk/TestPropertyAccess.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; +import ch.bailu.gtk.adw.BreakpointBin; import ch.bailu.gtk.gdk.Display; import ch.bailu.gtk.gio.ListStore; import ch.bailu.gtk.glib.Glib; @@ -94,4 +95,17 @@ public void testPropertyAccessBox() { assertEquals(box.getCanFocus(), box.getBooleanProperty("homogeneous")); assertTrue(box.getHomogeneous()); } + + @Test + @EnabledIf("gtkInit") + public void testPropertyAccessBreakpointBin() { + var breakpointBin = new BreakpointBin(); + + for (int i=0; i< 10000; i++) { + breakpointBin.setIntProperty("width-request", i); + breakpointBin.setIntProperty("height-request", i); + + assertEquals(i, breakpointBin.getIntProperty("width-request")); + } + } }