diff --git a/src/cairo.jl b/src/cairo.jl index 586ca05d..3ce9e57b 100644 --- a/src/cairo.jl +++ b/src/cairo.jl @@ -1,4 +1,5 @@ -import Cairo: CairoSurface, CairoContext, CairoARGBSurface +import Cairo: CairoSurface, CairoContext, CairoARGBSurface, set_source_surface, paint +import ..GLib: _GObjectClass using Cairo_jll using ..Pango.Cairo @@ -89,6 +90,118 @@ function resize(config::Function, widget::GtkCanvas) nothing end +### CairoCanvas is experimental and unstable + +mutable struct CairoCanvas <: GtkWidget + handle::Ptr{GObject} + content_size::Tuple{Int,Int} + is_sized::Bool + resize::Union{Function, Nothing} + draw::Union{Function, Nothing} + back::CairoSurface # backing store + backcc::CairoContext + + function CairoCanvas(handle::Ptr{GObject}, w::Integer = -1, h::Integer = -1, init_back::Bool = false) + content_size = (0,0) + if w > 0 && h > 0 + content_size = (w,h) + elseif init_back + error("Width and height arguments must be provided to immediately initialize CairoCanvas.") + end + widget = new(handle, content_size, false, nothing, nothing) + if init_back + _init_canvas!(widget, w, h) + end + + return widget + end +end + +""" + getgc(c::GtkCanvas) + +Return the CairoContext of the `CairoSurface` backing store of a `GtkCanvas`. +""" +function getgc(c::Union{GtkCanvas,CairoCanvas}) + isdefined(c,:backcc) || error("GtkCanvas not yet initialized.") + return c.backcc +end + +CairoContext(cr::cairoContext) = CairoContext(Ptr{Nothing}(cr.handle)) +Cairo.cairoContext(cr::CairoContext) = cairoContext(Ptr{cairoContext}(cr.ptr),false) + +function widget_measure(widget_ptr::Ptr{GObject}, orientation::Cint, for_size::Cint, minimum::Ptr{Cint}, natural::Ptr{Cint}, minimum_baseline::Ptr{Cint}, natural_baseline::Ptr{Cint}) + canvas = convert(CairoCanvas, widget_ptr) + s = if orientation == Orientation_HORIZONTAL + canvas.content_size[1] + else + canvas.content_size[2] + end + unsafe_store!(minimum, s) + unsafe_store!(natural, s) + unsafe_store!(natural_baseline, Cint(-1)) + nothing +end + +function widget_size_allocate(widget_ptr::Ptr{GObject}, width::Cint, height::Cint, baseline::Cint) + canvas = convert(CairoCanvas, widget_ptr) + _init_canvas!(canvas, width, height) + canvas.is_sized = true + + if isa(canvas.resize, Function) + canvas.resize(canvas) + end + draw(canvas) + nothing +end + +function widget_snapshot(widget_ptr::Ptr{GObject}, snapshot_ptr::Ptr{GObject}) + canvas = convert(CairoCanvas, widget_ptr) + snapshot = convert(GtkSnapshot, snapshot_ptr) + w,h = size(canvas) + cr = Gtk4.G_.append_cairo(snapshot, Ref(_GrapheneRect(0,0,w,h))) + cc = CairoContext(Ptr{Nothing}(cr.handle)) + set_source_surface(cc, canvas.back) + paint(cc) + nothing +end + +function object_class_init(class::Ptr{_GObjectClass}, user_data) + widget_klass_ptr = Ptr{_GtkWidgetClass}(class) + widget_klass = unsafe_load(widget_klass_ptr) + widget_klass.snapshot = @cfunction(widget_snapshot, Cvoid, (Ptr{GObject}, Ptr{GObject})) + widget_klass.measure = @cfunction(widget_measure, Cvoid, (Ptr{GObject}, Cint, Cint, Ptr{Cint}, Ptr{Cint}, Ptr{Cint}, Ptr{Cint})) + widget_klass.size_allocate = @cfunction(widget_size_allocate, Cvoid, (Ptr{GObject}, Cint, Cint, Cint)) + unsafe_store!(widget_klass_ptr, widget_klass) + nothing +end + +function GLib.g_type(::Type{T}) where T <: CairoCanvas + gt = GLib.g_type_from_name(:CairoCanvas) + if gt > 0 + return gt + else + object_class_init_cfunc = @cfunction(object_class_init, Cvoid, (Ptr{_GObjectClass}, Ptr{Cvoid})) + return GLib.register_subtype(GtkWidget, :CairoCanvas, object_class_init_cfunc) + end +end + +function CairoCanvas(w::Integer = -1, h::Integer = -1, init_back::Bool = false; kwargs...) + widget = GLib.gobject_new(CairoCanvas, w, h, init_back) + GLib.setproperties!(widget; kwargs...) + widget +end + +""" + cairo_surface(c::GtkCanvas) + +Return the image `CairoSurface` backing store for a `GtkCanvas`. +""" +function cairo_surface(c::Union{GtkCanvas,CairoCanvas}) + isdefined(c,:back) || error("Canvas not yet initialized.") + return c.back +end + """ draw(redraw::Function, widget::GtkCanvas) @@ -97,7 +210,7 @@ needs to be redrawn. The function should have a single argument, the `GtkCanvas`, from which the `CairoSurface` can be retrieved using [`getgc`](@ref). """ -function draw(redraw::Function, widget::GtkCanvas) +function draw(redraw::Function, widget::Union{GtkCanvas,CairoCanvas}) widget.draw = redraw draw(widget) nothing @@ -108,7 +221,7 @@ end Triggers a redraw of the canvas using a previously set `redraw` function. """ -function draw(widget::GtkCanvas) +function draw(widget::Union{GtkCanvas,CairoCanvas}) if !isdefined(widget, :back) #@warn("backing store not defined") return @@ -119,25 +232,3 @@ function draw(widget::GtkCanvas) G_.queue_draw(widget) end -""" - getgc(c::GtkCanvas) - -Return the CairoContext of the `CairoSurface` backing store of a `GtkCanvas`. -""" -function getgc(c::GtkCanvas) - isdefined(c,:backcc) || error("GtkCanvas not yet initialized.") - return c.backcc -end - -""" - cairo_surface(c::GtkCanvas) - -Return the image `CairoSurface` backing store for a `GtkCanvas`. -""" -function cairo_surface(c::GtkCanvas) - isdefined(c,:back) || error("GtkCanvas not yet initialized.") - return c.back -end - -CairoContext(cr::cairoContext) = CairoContext(Ptr{Nothing}(cr.handle)) -Cairo.cairoContext(cr::CairoContext) = cairoContext(Ptr{cairoContext}(cr.ptr),false) diff --git a/test/gui/canvas.jl b/test/gui/canvas.jl index 0633f5e9..e6786595 100644 --- a/test/gui/canvas.jl +++ b/test/gui/canvas.jl @@ -93,3 +93,29 @@ c = GtkCanvas(10,10,true) @test isdefined(c,:back) end +@testset "CairoCanvas initialization" begin +# must provide a nonzero size to use `init_back` +@test_throws ErrorException c = Gtk4.CairoCanvas(0,0,true) +c = Gtk4.CairoCanvas(10,10,true) +@test isdefined(c,:back) +end + +@testset "SetCoordinates CairoCanvas" begin + cnvs = Gtk4.CairoCanvas(300, 280) + sleep(0.2) + draw(cnvs) do c + set_coordinates(getgc(c), BoundingBox(0, 1, 0, 1)) + end + win = GtkWindow(cnvs) + sleep(1.0) + s = size(cnvs) + @test s[1] == 300 + @test s[2] == 280 + mtrx = Cairo.get_matrix(getgc(cnvs)) + @test mtrx.xx == 300 + @test mtrx.yy == 280 + @test mtrx.xy == mtrx.yx == mtrx.x0 == mtrx.y0 == 0 + surf = Gtk4.cairo_surface(cnvs) + destroy(win) +end +