/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/// Create a `std::ffi::CStr` directly from a literal string. /// /// The argument is an `expr` rather than `literal` so that other macros can be used (such as /// `stringify!`).
macro_rules! cstr {
( $str:expr ) => { #[allow(unused_unsafe)] unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes()) }
.as_ptr()
};
}
impl UI { pubfn run_loop(&self, app: Application) { unsafe { let stream = gtk::g_memory_input_stream_new_from_data( super::icon::PNG_DATA.as_ptr() as _, // unwrap() because the PNG_DATA length will be well within gssize limits (32-bit // at the smallest). super::icon::PNG_DATA.len().try_into().unwrap(),
None,
); let icon_pixbuf =
gtk::gdk_pixbuf_new_from_stream(stream, std::ptr::null_mut(), std::ptr::null_mut());
gtk::g_object_unref(stream as _);
gtk::gtk_window_set_default_icon(icon_pixbuf);
let app_ptr = gtk::gtk_application_new(
std::ptr::null(),
gtk::GApplicationFlags_G_APPLICATION_FLAGS_NONE,
);
gtk::g_signal_connect_data(
app_ptr as *mut _,
cstr!("activate"),
Some(std::mem::transmute(
render_application asfor<'a> unsafe extern "C" fn(*mut gtk::GtkApplication, &'a Application),
)),
&app as *const Application as *mut Application as _,
None, 0,
); self.running.store(true, Relaxed);
gtk::g_application_run(app_ptr as *mut gtk::GApplication, 0, std::ptr::null_mut()); self.running.store(false, Relaxed);
gtk::g_object_unref(app_ptr as *mut _);
gtk::g_object_unref(icon_pixbuf as _);
}
}
pubfn invoke(&self, f: model::InvokeFn) { if !self.running.load(Relaxed) {
log::debug!("ignoring `invoke` as main loop isn't running"); return;
} type BoxedData = Option<model::InvokeFn>;
unsafeextern"C"fn call(ptr: gtk::gpointer) -> gtk::gboolean { let f: &mut BoxedData = ToPointer::from_ptr(ptr as _);
f.take().unwrap()(); false.into()
}
unsafeextern"C"fn drop(ptr: gtk::gpointer) { let _: Box<BoxedData> = ToPointer::from_ptr(ptr as _);
}
/// Types that can be converted to and from a pointer. /// /// These types must be sized to avoid fat pointers (i.e., the pointers must be FFI-compatible, the /// same size as usize). trait ToPointer: Sized { /// Convert the value to a pointer, passing ownership. fn to_ptr(self) -> *mut (); /// # Safety /// The caller must ensure that the pointer was created as the result of `to_ptr` on the same /// or a compatible type, and that the data is still valid. unsafefn from_ptr(ptr: *mut ()) -> Self;
}
/// Types that can be attached to a GLib object to be dropped when the widget is dropped. trait DropWithObject: Sized { fn drop_with_object(self, object: *mut gtk::GObject); fn drop_with_widget(self, widget: *mut gtk::GtkWidget) { self.drop_with_object(widget as *mut _);
}
impl Drop for GSource { fn drop(&mutself) { unsafe {
gtk::g_source_remove(self.0);
}
}
}
/// Connect a GTK+ object signal to a function, providing an additional context value (by /// reference).
macro_rules! connect_signal {
( object $object:expr ; with $with:expr ;
signal $name:ident ($target:ident : &$type:ty $(, $argname:ident : $argtype:ty )* ) $( -> $ret:ty )? $body:block
) => {{ unsafeextern"C"fn $name($($argname : $argtype ,)* $target: &$type) $( -> $ret )? $body #[allow(unused_unsafe)] unsafe {
gtk::g_signal_connect_data(
$object as *mut _,
cstr!(stringify!($name)),
Some(std::mem::transmute(
$name asfor<'a> unsafe extern "C" fn(
$($argtype,)*
&'a $type,
) $( -> $ret )?,
)),
$with as *const $typeas *mut $typeas _,
None, 0,
);
}
}};
}
/// Bind a read only (from the renderer perspective) property to a widget. /// /// The `set` function is called initially and when the property value changes.
macro_rules! property_read_only {
( property $property:expr ; fn set( $name:ident : & $type:ty ) $setbody:block
) => {{ let prop: &Property<$type> = $property; match prop {
Property::Static($name) => $setbody,
Property::Binding(v) => {
{ let $name = v.borrow();
$setbody
}
v.on_change(move |$name| $setbody);
}
Property::ReadOnly(_) => (),
}
}};
}
/// Bind a read/write property to a widget signal. /// /// This currently only allows signals which are of the form /// `void(*)(SomeGtkObject*, gpointer user_data)`.
macro_rules! property_with_signal {
( object $object:expr ; property $property:expr ; signal $signame:ident ; fn set( $name:ident : & $type:ty ) $setbody:block fn get( $getobj:ident : $gettype:ty ) -> $result:ty $getbody:block
) => {{ let prop: &Property<$type> = $property; match prop {
Property::Static($name) => $setbody,
Property::Binding(v) => {
{ let $name = v.borrow();
$setbody
} let changing = Rc::new(RefCell::new(false)); struct SignalData {
changing: Rc<RefCell<bool>>,
value: Synchronized<$result>,
} let signal_data = Box::new(SignalData {
changing: changing.clone(),
value: v.clone(),
});
v.on_change(move |$name| { if !*changing.borrow() {
*changing.borrow_mut() = true;
$setbody;
*changing.borrow_mut() = false;
}
});
connect_signal! {
object $object;
with signal_data.as_ref();
signal $signame(signal_data: &SignalData, $getobj: $gettype) { let new_value = (|| $getbody)(); if !*signal_data.changing.borrow() {
*signal_data.changing.borrow_mut() = true;
*signal_data.value.borrow_mut() = new_value;
*signal_data.changing.borrow_mut() = false;
}
}
}
signal_data.drop_with_object($object as _);
}
Property::ReadOnly(v) => {
v.register(move |target: &mut $result| { let $getobj: $gettype = $object as _;
*target = (|| $getbody)();
});
}
}
}};
}
unsafeextern"C"fn render_application(app_ptr: *mut gtk::GtkApplication, app: &Application) { unsafe {
gtk::gtk_widget_set_default_direction(if app.rtl {
gtk::GtkTextDirection_GTK_TEXT_DIR_RTL
} else {
gtk::GtkTextDirection_GTK_TEXT_DIR_LTR
});
} for window in &app.windows { let window_ptr = render_window(&window.element_type); let style = &window.style;
// Set size before applying style (since the style will set the visibility and show the // window). Note that we take the size request as an initial size here. // // `gtk_window_set_default_size` doesn't work; it resizes to the size request of the inner // labels (instead of wrapping them) since it doesn't know how small they should be (that's // dictated by the window size!). unsafe {
gtk::gtk_window_resize(
window_ptr as _,
style
.horizontal_size_request
.map(|v| v as i32)
.unwrap_or(-1),
style.vertical_size_request.map(|v| v as i32).unwrap_or(-1),
);
}
fn alignment(align: &Alignment) -> gtk::GtkAlign { use Alignment::*; match align {
Start => gtk::GtkAlign_GTK_ALIGN_START,
Center => gtk::GtkAlign_GTK_ALIGN_CENTER,
End => gtk::GtkAlign_GTK_ALIGN_END,
Fill => gtk::GtkAlign_GTK_ALIGN_FILL,
}
}
// Attach the state so that we can access it in the changed signal. // This is kind of ugly; in the future it might be a nicer developer interface to simply // always use a closure as the user data (which itself can capture arbitrary things). This // would move the data management from GTK to rust.
state.set_data(buffer as *mut _, cstr!("textview-state"));
property_with_signal! {
object buffer;
property content;
signal changed; fn set(new_value: &String) { unsafe {
gtk::gtk_text_buffer_set_text(buffer, new_value.as_ptr() as *const c_char, new_value.len().try_into().unwrap());
}
} fn get(buffer: *mut gtk::GtkTextBuffer) -> String { let state = unsafe {
gtk::g_object_get_data(buffer as *mut _, cstr!("textview-state"))
}; if !state.is_null() { let s: &State = unsafe { &*(state as *mut State as *const State) }; iflet Some(placeholder) = &s.placeholder { if *placeholder.visible.borrow() { return"".to_owned();
}
}
} letmut start_iter = gtk::GtkTextIter::default(); unsafe {
gtk::gtk_text_buffer_get_start_iter(buffer, &mut start_iter);
}
letmut s = String::new(); loop { let c = unsafe { gtk::gtk_text_iter_get_char(&start_iter) }; if c == 0 { break;
} // Safety: // gunichar is guaranteed to be a valid unicode codepoint (if nonzero).
s.push(unsafe { char::from_u32_unchecked(c) }); unsafe {
gtk::gtk_text_iter_forward_char(&mut start_iter);
}
}
s
}
}
text_ptr
}
Scroll(model::Scroll { content }) => { let scroll_ptr = unsafe { gtk::gtk_scrolled_window_new(std::ptr::null_mut(), std::ptr::null_mut()) }; unsafe {
gtk::gtk_scrolled_window_set_policy(
scroll_ptr as *mut _,
gtk::GtkPolicyType_GTK_POLICY_NEVER,
gtk::GtkPolicyType_GTK_POLICY_ALWAYS,
);
gtk::gtk_scrolled_window_set_shadow_type(
scroll_ptr as *mut _,
gtk::GtkShadowType_GTK_SHADOW_IN,
);
}; iflet Some(widget) = content.as_deref().and_then(render) { unsafe {
gtk::gtk_container_add(scroll_ptr as *mut gtk::GtkContainer, widget);
}
}
scroll_ptr
}
Progress(model::Progress { amount }) => { let progress_ptr = unsafe { gtk::gtk_progress_bar_new() };
property_read_only! {
property amount; fn set(value: &Option<f32>) { // FIXME: The logic here assumes that if `amount` is None, it will remain that // way. Specifically, if we were to change back and forth, additional pulse // callbacks would be registered, and they are not be removed when changing to // a specific fraction. As this property is currently used, we don't encounter // this case. match &*value {
Some(v) => unsafe {
gtk::gtk_progress_bar_set_fraction(
progress_ptr as *mut _,
v.clamp(0f32,1f32) as f64,
);
}
None => unsafe {
gtk::gtk_progress_bar_pulse(progress_ptr as *mut _);
// This will call even when the progress bar is hidden, but it's of // little consequence. let source = GSource(gtk::g_timeout_add(100, Some(pulse), progress_ptr as _));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.