HOWTO:用于gtk(rust-gnome)回调的地理生锈 [英] HOWTO: Idiomatic Rust for callbacks with gtk (rust-gnome)
问题描述
我目前正在学习Rust并希望使用它来开发基于GUI
的GTK +应用程序。我的问题涉及将回调注册为
,以响应GTK事件/信号以及这些回调中的变异状态。
我有一个工作但不雅的解决方案,所以我想问问
是否更清洁,更习惯解决方案。
我已经实施我的代码作为一个结构与方法实现,其中
结构保持对GTK小部件的引用以及它需要的其他状态
。它构造一个传递给
GtkWidget :: connect *
函数的闭包,以便接收事件,绘制到
画布等。可能会导致借款检查问题,因为我现在将
解释。我有一些正在工作,但(恕我直言)非理想的代码,我会
显示。
初始的非工作解决方案:
$ b
#![cfg_attr(not(feature =gtk_3_10 ),允许(unused_variables,unused_mut))]
;它现在需要
extern crate gtk;
extern crate cairo;
使用gtk :: traits :: *;
使用gtk :: signal :: Inhibit;
使用cairo :: {Context,RectangleInt};
结构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(开罗API测试);
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();
返回实例;
fn exit_on_close(&self){
self.window.connect_delete_event(| _,_ | {
gtk :: main_quit();
禁止(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(他们现在唯一能承受诅咒的诅咒就是'臭虫关闭'--PTerry);
cairo_ctx.restore();
}
fn on_size_allocate(& mut self,rect:& RectangleInt){
self.width = rect.width as i32;
self.height = rect.height as i32;
$ b fn main(){
gtk :: init()。unwrap_or_else(| _ | panic!(未能初始化GTK))。
println!(Major:{},Minor:{},gtk :: get_major_version(),gtk :: get_minor_version());
let window = RenderingAPITestWindow :: new(800,500);
window.exit_on_close();
gtk :: main();
$ b以上不能编译为
RenderingAPITestWindow :: new
创建并传递给调用
GtkWidget :: connect *
方法试图借用实例
。
编译器指出,闭包可能超过了其中声明了
并且instance
由外部函数拥有的函数
因此这个问题。鉴于GTK可能会在未指定的时间内引用这些闭包
,所以我们需要一种可以在运行时确定
生命周期的方法,因此我的下一个问题是$ b $其中RenderingAPITestWindow
实例被封装在
Rc< RefCell< ...>>< / code> 。
包装
RenderingAPITestWindow
实例编译,但在运行时死掉:#![cfg_attr(not(feature =gtk_3_10),allow(unused_variables,unused_mut))]
extern crate gtk;
extern crate cairo;
使用std :: rc :: Rc;
使用std :: cell :: RefCell;
使用gtk :: traits :: *;
使用gtk :: signal :: Inhibit;
使用cairo :: {Context,RectangleInt};
结构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(开罗API测试);
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();
禁止(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();
禁止(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(他们现在唯一能承受诅咒的诅咒就是'臭虫关闭'--PTerry);
cairo_ctx.restore();
}
fn on_size_allocate(& mut self,rect:& RectangleInt){
self.width = rect.width as i32;
self.height = rect.height as i32;
$ b fn main(){
gtk :: init()。unwrap_or_else(| _ | panic!(未能初始化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();
}
上述解决方案可以编译,但并不特别漂亮:
RenderingAPITestWindow :: new
返回
Rc< RefCell< RenderingAPITestWindow> ;>
而不是
RenderingAPITestWindow
- 访问<$ c的字段和方法由于必须打开
Rc< RefCell< ...>>
,所以$ c> RenderingAPITestWindow
wrapped_instance.borrow()。some_method(...)
,而不仅仅是
instance.some_method(。 ..)
wrapped_instance
;尝试使用来使用
wrapped_instance
会尝试借用一个对象 - 包装而不是
RenderingAPITestWindow
这一次 - 即由
RenderingAPITestWindow :: new
拥有,与以前一样 在上面的编译过程中,它会在运行时死亡:
thread'< main>'panicked at RefCell< T>已借入',../src/libcore/cell.rs:442
发生未知错误
这是由于调用 window.show_all()
导致GTK到
初始化窗口小部件层次结构,导致绘图区窗口小部件
收到 size-allocate
事件。访问调用
show_all()
的窗口要求 Rc< RefCell< ...>> $ c $ (因此
wrapped_instance.borrow()。window.show_all();
)和实例
被借用。当借位结束前,当 show_all()
返回时,GTK调用
绘图区的 size-allocate
事件处理程序,这导致关闭
连接到它(上面4行)被调用。关闭试图对
借用对 RenderingAPITestWindow
实例
( wrapped_instance_for_sizealloc.borrow_mut())的可变引用。on_size_allocate(rect) ;
)
为了调用 on_size_allocate
方法。这试图借用
可变引用,而第一个不可变引用仍在范围内。
第二次借用导致运行时间恐慌。
正在运行但是 - 我设法获得的恕我直言 - 到目前为止,
的工作是将 RenderingAPITestWindow
分解为两个结构体,其中
是由回调修改的可变状态,移动到
单独的结构。
分裂 RenderingAPITestWindow
结构的工作但不雅的解决方案:
#![cfg_attr(not(feature =gtk_3_10),allow(unused_variables,unused_mut))]
extern crate gtk;
extern crate cairo;
使用std :: rc :: Rc;
使用std :: cell :: RefCell;
使用gtk :: traits :: *;
使用gtk :: signal :: Inhibit;
使用cairo :: {Context,RectangleInt};
struct RenderingAPITestWindowState {
width:i32,
height:i32
}
impl RenderingAPITestWindowState {
新(宽度:i32,高度: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(他们现在唯一能承受诅咒的诅咒就是'臭虫关闭'--PTerry);
cairo_ctx.restore();
}
fn on_size_allocate(& mut self,rect:& RectangleInt){
self.width = rect.width as i32;
self.height = rect.height as i32;
$ b struct RenderingAPITestWindow {
window:gtk :: Window,
drawing_area:gtk :: DrawingArea,
状态: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(开罗API测试);
window.add(& drawing_area);
让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();
禁止(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();
禁止(true)
});
$ b fn main(){
gtk :: init()。unwrap_or_else(| _ | panic!(未能初始化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();
}
虽然上面的代码可以根据需要运行,但我希望找到更好的方法前进
;我想问一下,如果有人知道更好的方法为
,则上述过程使编程过程变得相当复杂,需要
使用 Rc< RefCell< ...> >
并拆分结构以满足Rust的借用规则。
pre $ code $#[cfg_attr(not(feature =gtk_3_10),allow(unused_variables,unused_mut))]
extern crate gtk;
extern crate cairo;
使用std :: rc :: Rc;
使用std :: cell :: RefCell;
使用gtk :: traits :: *;
使用gtk :: signal :: Inhibit;
使用cairo :: {Context,RectangleInt};
结构RenderingAPITestWindow {
window:gtk :: Window,
drawing_area:gtk :: DrawingArea,
state:RefCell< RenderingState> ;,
struct RenderingState {
width:i32,
height:i32,
}
impl RenderingAPITestWindow {
新(宽度:i32,高度: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(开罗API测试);
window.add(& drawing_area);
let instance = Rc :: new(RenderingAPITestWindow {
window:window,
drawing_area:drawing_area,
state:RefCell :: new(RenderingState {
宽度:宽度,
高度:高度,
}),
});
{
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();
禁止(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();
实例
}
fn exit_on_close(&self){
self.window.connect_delete_event(| _,_ | {
gtk :: main_quit ();
禁止(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(他们现在唯一能承受诅咒的诅咒就是'臭虫关闭'--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!(无法初始化GTK。) );
println!(Major:{},Minor:{},gtk :: get_major_version(),gtk :: get_minor_version());
let window = RenderingAPITestWindow :: new(800,500);
window.exit_on_close();
gtk :: main();
}
我通过一些观察得出了这个结论:
- 实例正在多个闭包中共享一段不确定的时间。
Rc
是该场景的权利答案,因为它提供共享所有权。Rc
使用起来非常符合人体工程学;它和任何其他指针类型一样。 - 实际上发生了变异的
instance 中的唯一部分就是您的状态。由于您的实例正在共享,因此无法使用标准
& mut
指针进行可变的借用。因此,您必须使用内部可变性。这是RefCell
提供的内容。请注意,你只需要在你变异的状态下使用RefCell
。所以这仍然将状态分离成单独的结构,但它很好地适用于IMO。 - 可能对此代码进行的修改是添加
#[derive(Clone,复制)]
添加到RenderingState
结构的定义中。由于它可以是Copy
(因为它的所有组件类型都是Copy
),所以可以使用Cell
而不是RefCell
。
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 anRc<RefCell<RenderingAPITestWindow>>
rather than aRenderingAPITestWindow
- Accessing fields and methods of
RenderingAPITestWindow
is complicated by the fact that theRc<RefCell<...>>
must be opened up; it now requireswrapped_instance.borrow().some_method(...)
rather than justinstance.some_method(...)
- Each closure requires it's own clone of
wrapped_instance
; attempting to usewrapped_instance
would attempt to borrow an object -- the wrapper rather than theRenderingAPITestWindow
this time -- that is owned byRenderingAPITestWindow::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 whatRefCell
provides. Note though, that you only need to useRefCell
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 theRenderingState
struct. Since it can beCopy
(because all of its component types areCopy
), you can useCell
instead ofRefCell
.
这篇关于HOWTO:用于gtk(rust-gnome)回调的地理生锈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!