HOWTO:用于gtk(rust-gnome)回调的地理生锈 [英] HOWTO: Idiomatic Rust for callbacks with gtk (rust-gnome)

查看:205
本文介绍了HOWTO:用于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< ...>> 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 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.

这篇关于HOWTO:用于gtk(rust-gnome)回调的地理生锈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆