It seems like the best option is to implement a custom type to use as model column together with a custom renderer. The custom type holds an optional String and an optional Pixbuf where one of those two parameters should be set to some value. I did not use an enum to ensure compatibility with glib-types (maybe it would also be possible this way but I am unsure). The custom renderer holds renderers for Strings and Pixbufs and can dynamically choose which one to use depending on the given custom type.
Basically, besides of storage in the custom type the only functionality to be added is providing functions to render a cell and to provide an entry's preferred size. In those cases the content of the custom type is used to decide from which of the already given renderers the behaviour regarding rendering- and size-request-functionality should be copied.
Implementing the custom type and renderer is a bit challenging as the documentation of gtk-rs is not the best especially regarding using subclasses of renderers for instance. My code is based on the examples for subclasses and a custom model from the gtk-rs example repo. Due to a lack of experience with Rust and gtk on my side, there may be a lot of parts which could be solved more elegant and consistent, but I'd like to share my solution in case anyone having a similar problem is searching for a direction on how to proceed:
#[macro_use]
extern crate glib;
extern crate gdk_pixbuf;
extern crate gio;
extern crate gtk;
use gio::prelude::*;
use gtk::prelude::*;
use custom_glib_string_or_pixbuf::StringOrPixbuf;
use custom_glib_string_or_pixbuf_renderer::StringOrPixbufRenderer;
use gdk_pixbuf::Pixbuf;
// Content type
mod custom_glib_string_or_pixbuf {
use gdk_pixbuf::Pixbuf;
use gio::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
mod internal {
use super::*;
use std::cell::RefCell;
pub struct StringOrPixbuf {
string: RefCell<Option<String>>,
pixbuf: RefCell<Option<Pixbuf>>,
}
static PROPERTIES: [subclass::Property; 2] = [
subclass::Property("string", |name| {
glib::ParamSpec::string(name, "String", "String", None, glib::ParamFlags::READWRITE)
}),
subclass::Property("pixbuf", |name| {
glib::ParamSpec::object(
name,
"Pixbuf",
"Pixbuf",
Pixbuf::static_type(),
glib::ParamFlags::READWRITE,
)
}),
];
impl ObjectSubclass for StringOrPixbuf {
const NAME: &'static str = "StringOrPixbuf";
type ParentType = glib::Object;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(class: &mut Self::Class) {
class.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
string: RefCell::new(None),
pixbuf: RefCell::new(None),
}
}
}
impl ObjectImpl for StringOrPixbuf {
glib_object_impl!();
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("string", ..) => {
self.string.replace(value.get().unwrap());
}
subclass::Property("pixbuf", ..) => {
self.pixbuf.replace(value.get().unwrap());
}
_ => panic!("Tried to set unknown property of StringOrPixbuf"),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("string", ..) => Ok(self.string.borrow().to_value()),
subclass::Property("pixbuf", ..) => Ok(self.pixbuf.borrow().to_value()),
_ => panic!("Tried to get unknown property of StringOrPixbuf"),
}
}
}
}
glib_wrapper! {
pub struct StringOrPixbuf(Object<subclass::simple::InstanceStruct<internal::StringOrPixbuf>,subclass::simple::ClassStruct<internal::StringOrPixbuf>, StringOrPixbufClass>);
match fn {
get_type => || internal::StringOrPixbuf::get_type().to_glib(),
}
}
impl StringOrPixbuf {
pub fn new(string: Option<String>, pixbuf: Option<Pixbuf>) -> StringOrPixbuf {
glib::Object::new(
StringOrPixbuf::static_type(),
&[("string", &string), ("pixbuf", &pixbuf)],
)
.expect("Failed to create StringOrPixbuf instance")
.downcast()
.unwrap()
}
pub fn is_string(&self) -> bool {
let string_option = self
.get_property("string")
.unwrap()
.get::<String>()
.unwrap();
let pixbuf_option = self
.get_property("pixbuf")
.unwrap()
.get::<Pixbuf>()
.unwrap();
if string_option.is_some() == pixbuf_option.is_some() {
panic!("Illegal StringOrPixbuf-state")
} else if let Some(_) = string_option {
true
} else {
false
}
}
}
}
// Renderer
mod custom_glib_string_or_pixbuf_renderer {
use custom_glib_string_or_pixbuf::StringOrPixbuf;
use gdk_pixbuf::Pixbuf;
use gio::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
use gtk::prelude::*;
mod internal {
use super::*;
use std::cell::RefCell;
pub struct StringOrPixbufRenderer {
text_renderer: gtk::CellRendererText,
pixbuf_renderer: gtk::CellRendererPixbuf,
string_or_pixbuf: RefCell<Option<StringOrPixbuf>>,
}
static PROPERTIES: [subclass::Property; 1] =
[subclass::Property("string_or_pixbuf", |name| {
glib::ParamSpec::object(
name,
"string_or_pixbuf",
"string_or_pixbuf",
StringOrPixbuf::static_type(),
glib::ParamFlags::READWRITE,
)
})];
impl ObjectSubclass for StringOrPixbufRenderer {
const NAME: &'static str = "StringOrPixbufRenderer";
type ParentType = gtk::CellRenderer;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(class: &mut Self::Class) {
class.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
text_renderer: gtk::CellRendererText::new(),
pixbuf_renderer: gtk::CellRendererPixbuf::new(),
string_or_pixbuf: RefCell::new(None),
}
}
}
impl ObjectImpl for StringOrPixbufRenderer {
glib_object_impl!();
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("string_or_pixbuf", ..) => {
self.string_or_pixbuf.replace(value.get().unwrap());
}
_ => panic!("Tried to set unknown property of StringOrPixbufRenderer"),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("string_or_pixbuf", ..) => {
Ok(self.string_or_pixbuf.borrow().to_value())
}
_ => panic!("Tried to get unknown property of StringOrPixbufRenderer"),
}
}
}
impl gtk::subclass::cell_renderer::CellRendererImpl for StringOrPixbufRenderer {
fn render<P: IsA<gtk::Widget>>(
&self,
_renderer: >k::CellRenderer,
cr: &cairo::Context,
widget: &P,
background_area: &gdk::Rectangle,
cell_area: &gdk::Rectangle,
flags: gtk::CellRendererState,
) {
self.update_renderers();
if self.is_content_string() {
self.text_renderer
.render(cr, widget, background_area, cell_area, flags);
} else {
self.pixbuf_renderer
.render(cr, widget, background_area, cell_area, flags);
}
}
fn get_preferred_width<P: IsA<gtk::Widget>>(
&self,
_renderer: >k::CellRenderer,
widget: &P,
) -> (i32, i32) {
self.update_renderers();
if self.is_content_string() {
self.text_renderer.get_preferred_width(widget)
} else {
self.pixbuf_renderer.get_preferred_width(widget)
}
}
fn get_preferred_height<P: IsA<gtk::Widget>>(
&self,
_renderer: >k::CellRenderer,
widget: &P,
) -> (i32, i32) {
self.update_renderers();
if self.is_content_string() {
self.text_renderer.get_preferred_height(widget)
} else {
self.pixbuf_renderer.get_preferred_height(widget)
}
}
}
impl StringOrPixbufRenderer {
fn is_content_string(&self) -> bool {
self.string_or_pixbuf
.borrow()
.as_ref()
.expect("No StringOrPixbuf known to StringOrPixbufRenderer")
.is_string()
}
fn update_renderers(&self) {
if self.is_content_string() {
self.text_renderer.set_property_text(Some(
&self
.string_or_pixbuf
.borrow()
.as_ref()
.unwrap()
.get_property("string")
.unwrap()
.get::<String>()
.unwrap()
.unwrap()[..],
));
} else {
self.pixbuf_renderer.set_property_pixbuf(Some(
&self
.string_or_pixbuf
.borrow()
.as_ref()
.unwrap()
.get_property("pixbuf")
.unwrap()
.get::<Pixbuf>()
.unwrap()
.unwrap(),
));
}
}
}
}
glib_wrapper! {
pub struct StringOrPixbufRenderer(
Object<subclass::simple::InstanceStruct<internal::StringOrPixbufRenderer>,
subclass::simple::ClassStruct<internal::StringOrPixbufRenderer>,
SimpleAppWindowClass>)
@extends gtk::CellRenderer;
match fn {
get_type => || internal::StringOrPixbufRenderer::get_type().to_glib(),
}
}
impl StringOrPixbufRenderer {
pub fn new() -> StringOrPixbufRenderer {
glib::Object::new(StringOrPixbufRenderer::static_type(), &[])
.expect("Failed to create StringOrPixbufRenderer instance")
.downcast::<StringOrPixbufRenderer>()
.unwrap()
}
}
}
fn main() {
let application =
gtk::Application::new(None, Default::default())
.expect("failed to initialize GTK application");
application.connect_activate(|app| {
let window = gtk::ApplicationWindow::new(app);
window.set_title("Mixed Pixbuf and String ComboBox Demo");
window.set_default_size(350, 70);
let model = gtk::ListStore::new(&[StringOrPixbuf::static_type()]);
let combo = gtk::ComboBox::with_model(&model);
let row = model.append();
let image = Pixbuf::from_file("image.png")
.unwrap()
.scale_simple(100, 70, gdk_pixbuf::InterpType::Bilinear)
.unwrap();
model.set(&row, &[0], &[&StringOrPixbuf::new(None, Some(image))]);
combo.set_active(Some(0));
let row = model.append();
model.set(
&row,
&[0],
&[&StringOrPixbuf::new(
Some(String::from("Hello World")),
None,
)],
);
let row = model.append();
let image = Pixbuf::from_file("another_image.png")
.unwrap()
.scale_simple(100, 70, gdk_pixbuf::InterpType::Bilinear)
.unwrap();
model.set(&row, &[0], &[&StringOrPixbuf::new(None, Some(image))]);
let renderer = StringOrPixbufRenderer::new();
combo.pack_start(&renderer, true);
combo.add_attribute(&renderer, "string_or_pixbuf", 0);
window.add(&combo);
window.show_all();
});
application.run(&[]);
}