Related
I've tried to add the Group {} trick to get more than 10 elements in a Table view, but the fails to compile just like when there is more than 10 elements without the group.
var body: some View {
Table(viewModel.tableArrayOfStructs, sortOrder: $sortOrder) {
Group {
TableColumn("One", value: \.One).width(min: 35, ideal: 35, max: 60)
TableColumn("Two", value: \.Two).width(30)
TableColumn("Three", value: \.Three).width(50)
TableColumn("Four", value: \.Four).width(min: 150, ideal: 200, max: nil)
TableColumn("Five", value: \.Five).width(50)
TableColumn("Six", value: \.Six).width(min: 50, ideal: 55, max: nil)
TableColumn("Seven", value: \.Seven).width(88)
TableColumn("Eight", value: \.Eight).width(88)
TableColumn("Nine", value: \.Nine).width(20)
TableColumn("Ten", value: \.Ten).width(50)
}
TableColumn("Eleven", value: \.Eleven).width(50)
}
If I add the eleventh+ column also into a new group I get the same issue. The compiler reports:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
Is there any means to have a table with more than 10 columns short of dropping down to NSViewRepresentable?
Apple Developer Technical Support was able to help. They recommended being explicit with the value: type information in the TableColumn to give the compiler some help. Without explicit type info with or without the group the compiler simply gives up. I also found that once you add one group, you need to add all TableColumns to be within groups (which have 10 or less TableColums per group)
var body: some View {
Table(viewModel.tableArrayOfStructs, sortOrder: $sortOrder) {
Group {
TableColumn("One", value: \ViewModel.TableArrayOfStructs.One).width(min: 35, ideal: 35, max: 60)
TableColumn("Two", value: \ViewModel.TableArrayOfStructs.Two).width(30)
TableColumn("Three", value: \ViewModel.TableArrayOfStructs.Three).width(50)
TableColumn("Four", value: \ViewModel.TableArrayOfStructs.Four).width(min: 150, ideal: 200, max: nil)
TableColumn("Five", value: \ViewModel.TableArrayOfStructs.Five).width(50)
TableColumn("Six", value: \ViewModel.TableArrayOfStructs.Six).width(min: 50, ideal: 55, max: nil)
TableColumn("Seven", value: \ViewModel.TableArrayOfStructs.Seven).width(88)
TableColumn("Eight", value: \ViewModel.TableArrayOfStructs.Eight).width(88)
TableColumn("Nine", value: \ViewModel.TableArrayOfStructs.Nine).width(20)
TableColumn("Ten", value: \ViewModel.TableArrayOfStructs.Ten).width(50)
}
Group {
TableColumn("Eleven", value: \ViewModel.TableArrayOfStructs.Eleven).width(50)
}
}
Thank you very much for the tip of using Group. I was not sure it was going to work on TableColumn the same way as with other View but it works.
In my case, it was not compulsory to enclose all table columns within group.
I also receive the tip from Apple Developer Technical Support to be explicit about the value type: \ViewModel.property instead of .property.
We can also use computed property to generate a TableColumn like this:
var firstNameColumn: TableColumn<ViewModel, KeyPathComparator<ViewModel>, Text, Text> {
TableColumn("Firstname", value: \ViewModel.firstname)
}
The first Text refer to the \ViewModel.firstname that generates automatically a text while the second Text is for the label "FirstName"
In some case, when the type cannot be compare, you can replace KeyPathComparator by Never.
For instance for a date:
var dateColumn: TableColumn<ViewModel, Never, DateView, Text> {
TableColumn("Date") {doc in
DateView(date: doc.date)
}
}
Where DateView is a custom View.
Hope that helps others
Regards
Vincent
An example as requested in the comment (untested).
var body: some View {
Table(viewModel.tableArrayOfStructs, sortOrder: $sortOrder) {
Group {
self.foo //since foo has the #VB annotation, these will just be “pasted” in here.
}
self.foobar //not a #VB, so it returns a specific view
self.bar(count: …)
}
#ViewBuilder //not always necessary but usually very handy, depending on what you return
var foo: some View {
TableColumn("One", value: \.One).width(min: 35, ideal: 35, max: 60)
TableColumn("Two", value: \.Two).width(30)
TableColumn("Three", value: \.Three).width(50)
TableColumn("Four", value: \.Four).width(min: 150, ideal: 200, max: nil)
TableColumn("Five", value: \.Five).width(50)
TableColumn("Six", value: \.Six).width(min: 50, ideal: 55, max: nil)
TableColumn("Seven", value: \.Seven).width(88)
TableColumn("Eight", value: \.Eight).width(88)
TableColumn("Nine", value: \.Nine).width(20)
TableColumn("Ten", value: \.Ten).width(50)
}
var foobar: some View {
Group {
TableColumn("One", value: \.One).width(min: 35, ideal: 35, max: 60)
TableColumn("Two", value: \.Two).width(30)
TableColumn("Three", value: \.Three).width(50)
}
}
func bar(count: Int) -> some View
{
//as with foo, return your sub view
//annotate with #ViewBuilder if necessary
}
Point is, you don’t have to construct your entire view in the view’s body. Depending on programming style, code organisation,semantics, … ; split up your code into other child view structs or (private) vars and funcs within a view struct.
I'm wanting to refactor some lazy functional swift.
To explain the situation I'll explain the equivalent eager situation first:
let numbers = 1...10
do {
print("==================== EAGER INLINE =============================")
/// We start with a series of transformation on an array:
let result
= numbers
.filter { $0 >= 5 } /// Drop numbers less than 5
.map { $0 * $0 } /// Square the numbers
.flatMap { [$0, $0+1] } /// Insert number+1 after each number
.filter { $0 % 3 != 0 } /// Drop multiples of 3
print(result)
/// [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
/// which is [5^2, 5^2+1, 6^2+1, 7^2, 7^2+1, 8^2, 8^2+1, 9^2+1, 10^2, 10^2+1]
/// (Note 6^2 and 9^2 missing because they are divisible by 3)
}
We can refactor the map and the flatMap into a seperate function:
extension Array where Element == Int {
func squareAndInsert() -> [Int] {
self
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
}
}
do {
print("==================== EAGER REFACTOR =============================")
let result
= numbers
.filter { $0 >= 5 }
.squareAndInsert()
.filter { $0 % 3 != 0 }
print(result)
/// Gives exactly the same result: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
}
So now we'll repeat the process but lazily.
First inline:
do {
print("==================== LAZY INLINE =============================")
let result: some LazySequenceProtocol /// ": some LazySequenceprotocol" not strictly
/// required but without it my compiler grumbled about complexity so this is to give the
/// compiler a nudge in the right direction.
= numbers
.lazy /// Note the ".lazy" added here to make the array lazy.
.filter { $0 >= 5 }
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
.filter { $0 % 3 != 0 }
print(result)
}
Which prints:
LazyFilterSequence<FlattenSequence<LazyMapSequence<LazyMapSequence<LazyFilterSequence<ClosedRange<Int>>, Int>, Array<Int>>>>(_base: Swift.FlattenSequence<Swift.LazyMapSequence<Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>, Swift.Array<Swift.Int>>>(_base: Swift.LazyMapSequence<Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>, Swift.Array<Swift.Int>>(_base: Swift.LazyMapSequence<Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>, Swift.Int>(_base: Swift.LazyFilterSequence<Swift.ClosedRange<Swift.Int>>(_base: ClosedRange(1...10), _predicate: (Function)), _transform: (Function)), _transform: (Function))), _predicate: (Function))
Yikes!
Looks rather alarming at first sight but this is correct because unlike the eager result which is an array of Ints, the lazy result is an iterator which will provide us with the next number when we ask it to and this needs to know how to work back through all the function calls right back to the initial sequence. That's what this type is describing. Very nice now that we have the "some" keyword as in the past, if we wanted to put in an explicit type we would have to type all the above which is a bit of a mouthful !!
To see the list of numbers we need to force them to be calculated which we can do by putting the lazy sequence into an array: print(Array(result))
And this gives exactly the same result as before: [25, 26, 37, 49, 50, 64, 65, 82, 100, 101]
So now the challenge.
I want to refactor the lazy code in the same way that I did the eager code.
squareAndInsert needs to turn a LazySequenceProtocol<Int> into some LazySequenceProtocol so I try the code below but get various compile errors:
extension LazySequenceProtocol where Element == Int {
func squareAndInsertLazy() -> some LazySequenceProtocol {
self
.map { $0 * $0 }
.flatMap { [$0, $0+1] }
}
}
do {
print("==================== LAZY REFACTOR =============================")
let result: some LazySequenceProtocol // Error 1: Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
= numbers
.lazy
.filter { $0 >= 5 }
.squareAndInsertLazy() // Error 2: Value of type '[Int]' has no member 'squareAndInsertLazy'
.filter { $0 % 3 != 0 } // Error 3: Protocol type 'Any' cannot conform to 'LazySequenceProtocol' because only concrete types can conform to protocols
// Error 4: Value of type 'Any' has no member 'filter'
print(result)
}
I think Error 1 would probably go away if I fix the others.
I wonder if Error 2 means that trying to pass the lazy sequence into squareAndInsertLazy forces eagerness and this means [Int] is being presented to squareAndInsertLazy.
I can't work out how to move forward.
Any help appreciated.
The issue here is that LazySequenceProtocol is a PAT (protocol with associatedtype). So when you call squareAndInsertLazy() it returns some LazySequenceProtocol and it has no idea what the elements are anymore.
You can see this is the issue by commenting out your .filter { $0 % 3 != 0 } and replacing it with .filter { _ in true }. It will be perfectly happy and not complain because it doesn't care what the type of elements is in the sequence.
You can also see this using:
.filter { value in
let copy = value
return true
}
If you then Option click on copy it will show you the type is: (some LazySequenceProtocol).Element which cannot be used directly and must be inferred by the compiler. You can't do let copy: (some LazySequenceProtool).Element = value it won't compile.
So now that we have figured out what the problem is what are your possible solutions?
1) Don't return some PAT in this case some LazySequenceProtocol and return the concrete type which would be LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Self.Elements, Int>, [Int]>>>.
2) Go Back to inline.
3) Create a protocol that implements LazySequenceProtocol and refines Element to Int like this:
protocol LazySequenceOfInt: LazySequenceProtocol where Element == Int {}
extension LazySequence: LazySequenceOfInt where Element == Int {}
You will then use some LazySequenceOfInt. If you do this then you will potentially also want to extend the other Lazy types to conform to LazySequenceOfInt so they can also be used. In this particular case LazySequence is the only one you need though.
I have this structure,
typealias Tweak = (
grab:(_ p: SomeClass)->CGFloat,
change:(_ p: SomeClass, _ n: CGFloat)->(),
from:CGFloat, upto:CGFloat
)
(So, the first line "gives you a value", the second "changes something", the last two are just limits.)
So, you might have an array of such things ...
var tweaks:[Tweak] = [
({_ in 0}, {_ in}, 0,0),
( {p in mainHeight},
{p, n in
mainHeight = n
paintDisplayWhatever()},
8.0, 28.0),
( {p in Some.global},
{p, n in
Some.global = n
fireball.adjust(heat: n)},
8.0, 28.0),
etc
My question ...
notice the first one in the array, I simply wanted it to be the "nothing" version of a Tweak
So, I did this
nothingTweak: Tweak = ({_ in 0}, {_ in}, 0,0)
In Swift is there a better way to do the two "nothing" closures, or indeed, a more correct way to do the whole thing?
nothingTweak: Tweak = lessNilThanNil
you know?
There's no built-in value that represents "a closure which returns a 0 CGFloat value", let alone a tuple of them along with two other zero values.
However if you create a type, such as a struct, to represent a Tweak (rather than using a typealias of a tuple), you can define static constant(s) to represent "default" values of a Tweak. For example:
class SomeClass {}
struct Tweak {
// feel free to rename me to something more appropriate
static let zero = Tweak(grab: {_ in 0 }, change: {_ in }, from: 0, upto: 0)
var grab: (_ p: SomeClass) -> CGFloat
var change: (_ p: SomeClass, _ n: CGFloat) -> Void
var from: CGFloat
var upto: CGFloat
}
Now wherever a Tweak is expected, you can just say .zero to refer to your "zero default" (this is exactly what types like CGPoint do):
var tweaks: [Tweak] = [
.zero,
Tweak(
grab: { p in 25 },
change: { p, n in
print(p, n)
},
from: 8, upto: 28
),
Tweak(
grab: { p in 27 },
change: { p, n in
print(p, n * 4)
},
from: 8, upto: 28
)
]
Creating a structure is much more preferable to creating a typealias of a tuple in this case. Tuples are only really designed to be transient types, not to be used for long-term storage. Structures are much more powerful and suited for long-term use.
struct Mirror {
var size: Int
var name: String
}
let mirrors = [(name: "Dorian", size: 24), (name: "Corinth", size: 33), (name: "Cruyf", size: 29), (name: "Lola", size: 46)]
let smallMirrors = mirrors.filter {$0.size < 33 }
print(smallMirrors)
The problem here is that the result in Playgrounds is ["Dorian", 24 "Cruyf, 29"] - all marks included.
I want a list
Dorian 24
Cruyf 29
no "" or commas
You can just iterate with tuples through the smallMirrors:
for (k,v) in smallMirrors{
print(k, terminator: " ")
print(v)
}
Or an even better solution, using maps:
smallMirrors.map { (k,v) in
print(k, v)
}
Output:
Dorian 24
Cruyf 29
Hope it helps!
If you want no quotation marks and everything on the same line, you can use
import UIKit
struct Mirror {
var size: Int
var name: String
}
let mirrors = [(name: "Dorian", size: 24), (name: "Corinth", size: 33), (name: "Cruyf", size: 29), (name: "Lola", size: 46)]
let smallMirrors = mirrors.filter {$0.size < 33 }
smallMirrors.forEach{ print("\($0) \($1)", terminator:" " )}
Output:
Dorian 24 Cruyf 29
By modifying the "terminator" property of print, you can divide entries however you want. If you decide you do want each entry on a new line, just remove the terminator entirely. By taking this functional approach to the print statement, you have the flexibility to add any separators, punctuation, or other strings you may want in the future without issue.
I am currently learning Rust and looking to use it for developing a GUI
based application with GTK+. My problem relates to registering callbacks to
respond to GTK events/signals and mutating state within those callbacks.
I have a working but inelegant solution, so I would like to ask if there
is a cleaner, more idiomatic solution.
I have implemented my code as a struct with method implementations, where
the struct maintains references to the GTK widgets along with other state
that it needs. It constructs a closure that is passed to the
GtkWidget::connect* functions in order to receive events, draw to a
canvas, etc. This can cause problems with the borrow checker, as I will now
explain. I have some working but (IMHO) non-ideal code that I will
show.
Initial, non-working solution:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> RenderingAPITestWindow {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};
instance.drawing_area.connect_draw(|widget, cairo_context| {
instance.on_draw(cairo_context);
instance.drawing_area.queue_draw();
Inhibit(true)
});
instance.drawing_area.connect_size_allocate(|widget, rect| {
instance.on_size_allocate(rect);
});
instance.window.show_all();
return instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
}
The above fails to compile as the closures with
RenderingAPITestWindow::new that are created and passed to calls to
GtkWidget::connect* methods attempt to borrow instance. The
compiler states that the closures may outlive the function in which
they are declared and that instance is owned by the outer function,
hence the problem. Given that GTK may keep a reference to these closures
around for an unspecified amount of time, we need an approach in which the
lifetime can be determined at runtime, hence my next stab at the problem
in which the RenderingAPITestWindow instance is wrapped in
Rc<RefCell<...>>.
Wrapping the RenderingAPITestWindow instance compiles but dies at runtime:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_instance_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});
let wrapped_instance_for_sizealloc = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);
});
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}
The above solution compiles but its not particularly pretty:
RenderingAPITestWindow::new returns an
Rc<RefCell<RenderingAPITestWindow>> rather than a
RenderingAPITestWindow
Accessing fields and methods of RenderingAPITestWindow is complicated
by the fact that the Rc<RefCell<...>> must be opened up; it now requires
wrapped_instance.borrow().some_method(...) rather than just
instance.some_method(...)
Each closure requires it's own clone of wrapped_instance; attempting
to use wrapped_instance would attempt to borrow an object -- the
wrapper rather than the RenderingAPITestWindow this time -- that is
owned by RenderingAPITestWindow::new as before
While the above compiles, it dies at runtime with:
thread '<main>' panicked at 'RefCell<T> already borrowed', ../src/libcore/cell.rs:442
An unknown error occurred
This is due to the fact that the call to window.show_all() causes GTK to
initialise the widget hierarchy, resulting in the drawing area widget
receiving a size-allocate event. Accessing the window to call
show_all() required that the Rc<RefCell<...>> is opened (hence
wrapped_instance.borrow().window.show_all();) and the instance
borrowed. Before the borrow ends when show_all() returns, GTK invokes the
drawing area's size-allocate event handler, which causes the closure
connected to it (4 lines above) to be invoked. The closure attempts to
borrow a mutable reference to the RenderingAPITestWindow instance
(wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);)
in order to invoke the on_size_allocate method. This attempts to borrow a
mutable reference, while the first immutable reference is still in scope.
This second borrow causes the run-time panic.
The working but - IMHO - inelegant solution that I have managed to get
working so far is to split RenderingAPITestWindow into two structs, with
the mutable state that is to modified by the callbacks moved into a
separate struct.
Working but inelegant solution that splits the RenderingAPITestWindow struct:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindowState {
width: i32,
height: i32
}
impl RenderingAPITestWindowState {
fn new(width: i32, height: i32) -> RenderingAPITestWindowState {
return RenderingAPITestWindowState{width: width, height: height};
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: Rc<RefCell<RenderingAPITestWindowState>>
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let wrapped_state = Rc::new(RefCell::new(RenderingAPITestWindowState::new(width, height)))
;
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
state: wrapped_state.clone()
};
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_state_for_draw = wrapped_state.clone();
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_state_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});
let wrapped_state_for_sizealloc = wrapped_state.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_state_for_sizealloc.borrow_mut().on_size_allocate(rect);
});
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}
While the above code works as required, I would like to find a better way
for going forward; I would like to ask if anyone knows a better approach as
the above complicates the programming process a fair bit, with the need to
use Rc<RefCell<...>> and split structs to satisfy Rust's borrowing rules.
Here's a working version that I came up with:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: RefCell<RenderingState>,
}
struct RenderingState {
width: i32,
height: i32,
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RenderingAPITestWindow> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = Rc::new(RenderingAPITestWindow {
window: window,
drawing_area: drawing_area,
state: RefCell::new(RenderingState {
width: width,
height: height,
}),
});
{
let instance2 = instance.clone();
instance.drawing_area.connect_draw(move |widget, cairo_context| {
instance2.state.borrow().on_draw(cairo_context);
instance2.drawing_area.queue_draw();
Inhibit(true)
});
}
{
let instance2 = instance.clone();
instance.drawing_area.connect_size_allocate(move |widget, rect| {
instance2.state.borrow_mut().on_size_allocate(rect);
});
}
instance.window.show_all();
instance
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
}
impl RenderingState {
fn on_draw(&self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
}
I arrived at this through a few observations:
The instance is being shared across multiple closures for an undetermined amount of time. Rc is the right answer to that scenario because it provides shared ownership. Rc is very ergonomic to use; it works like any other pointer type.
The only part of instance that is actually mutated is your state. Since your instance is being shared, it cannot be borrowed mutably using the standard &mut pointer. Therefore, you must use interior mutability. This is what RefCell provides. Note though, that you only need to use RefCell on the state you're mutating. So this still separates out the state into a separate struct, but it works nicely IMO.
A possible modification to this code is to add #[derive(Clone, Copy)] to the definition of the RenderingState struct. Since it can be Copy (because all of its component types are Copy), you can use Cell instead of RefCell.