Initial Commit

This commit is contained in:
2024-01-14 22:23:28 -06:00
commit fb724d5def
17 changed files with 4083 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
**/*/target

1110
relm4_example/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

9
relm4_example/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "relm4_example"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
relm4 = { version = "0.7.0-beta.2" }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
[package]
name = "components"
version = "0.1.0"
edition = "2021"
[dependencies]
relm4 = { version = "0.7.0-beta.2" }

View File

@@ -0,0 +1,61 @@
use gtk::prelude::{GtkWindowExt, WidgetExt, DialogExt};
use relm4::{gtk, SimpleComponent, ComponentSender, ComponentParts};
pub struct DialogModel {
hidden: bool,
}
#[derive(Debug)]
pub enum DialogInput {
Show,
Accept,
Cancel,
}
#[derive(Debug)]
pub enum DialogOutput {
Close,
}
#[relm4::component(pub)]
impl SimpleComponent for DialogModel {
type Init = bool;
type Input = DialogInput;
type Output = DialogOutput;
view! {
gtk::MessageDialog {
set_modal: true,
#[watch]
set_visible: !model.hidden,
set_text: Some("Do you want to close before saving?"),
set_secondary_text: Some("All unsaved changes will be lost!"),
add_button: ("Close", gtk::ResponseType::Accept),
add_button: ("Cancel", gtk::ResponseType::Cancel),
connect_response[sender] => move |_, resp| {
sender.input(if resp == gtk::ResponseType::Accept {
DialogInput::Accept
} else {
DialogInput::Cancel
})
}
}
}
fn init(params: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let model = DialogModel { hidden: params };
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
match msg {
DialogInput::Show => self.hidden = false,
DialogInput::Accept => {
self.hidden = true;
sender.output(DialogOutput::Close).unwrap();
}
DialogInput::Cancel => self.hidden = true,
}
}
}

View File

@@ -0,0 +1,65 @@
use gtk::prelude::{ButtonExt, ToggleButtonExt, WidgetExt};
use relm4::{gtk, SimpleComponent, ComponentSender, ComponentParts};
pub struct HeaderModel;
#[derive(Debug)]
pub enum HeaderOutput {
View,
Edit,
Export,
}
#[relm4::component(pub)]
impl SimpleComponent for HeaderModel {
type Init = ();
type Input = ();
type Output = HeaderOutput;
view! {
#[root]
gtk::HeaderBar {
#[wrap(Some)]
set_title_widget = &gtk::Box {
add_css_class: "linked",
#[name = "group"]
gtk::ToggleButton {
set_label: "View",
set_active: true,
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.output(HeaderOutput::View).unwrap()
}
},
},
gtk::ToggleButton {
set_label: "Edit",
set_group: Some(&group),
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.output(HeaderOutput::Edit).unwrap()
}
}
},
gtk::ToggleButton {
set_label: "Export",
set_group: Some(&group),
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.output(HeaderOutput::Export).unwrap()
}
}
},
}
}
}
fn init(_params: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let model = HeaderModel;
let widgets = view_output!();
ComponentParts { model, widgets }
}
}

View File

@@ -0,0 +1,97 @@
mod header;
mod close_dialog;
use gtk::prelude::{GtkWindowExt, ApplicationExt};
use relm4::{gtk::{self, glib}, SimpleComponent, ComponentSender, ComponentParts, Controller, ComponentController, Component, RelmApp};
use header::{HeaderModel, HeaderOutput};
use close_dialog::{DialogModel, DialogInput, DialogOutput};
#[derive(Debug)]
enum AppMode {
View,
Edit,
Export,
}
#[derive(Debug)]
enum AppMsg {
SetMode(AppMode),
CloseRequest,
Close,
}
struct AppModel {
mode: AppMode,
header: Controller<HeaderModel>,
dialog: Controller<DialogModel>,
}
#[relm4::component]
impl SimpleComponent for AppModel {
type Init = AppMode;
type Input = AppMsg;
type Output = ();
view! {
main_window = gtk::Window {
set_default_width: 500,
set_default_height: 250,
set_titlebar: Some(model.header.widget()),
gtk::Label {
#[watch]
set_label: &format!("Placeholder for {:?}", model.mode),
},
connect_close_request[sender] => move |_| {
sender.input(AppMsg::CloseRequest);
glib::Propagation::Proceed
}
}
}
fn init(params: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let header: Controller<HeaderModel> = HeaderModel::builder()
.launch(())
.forward(sender.input_sender(), |msg| match msg {
HeaderOutput::View => AppMsg::SetMode(AppMode::View),
HeaderOutput::Edit => AppMsg::SetMode(AppMode::Edit),
HeaderOutput::Export => AppMsg::SetMode(AppMode::Export),
});
let dialog = DialogModel::builder()
.transient_for(root)
.launch(true)
.forward(sender.input_sender(), |msg| match msg {
DialogOutput::Close => AppMsg::Close,
});
let model = AppModel {
mode: params,
header,
dialog,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
match msg {
AppMsg::SetMode(mode) => {
self.mode = mode;
}
AppMsg::CloseRequest => {
self.dialog.sender().send(DialogInput::Show).unwrap();
}
AppMsg::Close => {
relm4::main_application().quit();
}
}
}
}
fn main() {
let relm = RelmApp::new("relm4.test.components");
relm.run::<AppModel>(AppMode::Edit);
}

View File

@@ -0,0 +1,89 @@
use gtk::glib::clone;
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt};
use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
#[derive(Debug)]
enum AppInput {
Increment,
Decrement,
}
struct AppModel {
counter: u8,
}
struct AppWidgets {
label: gtk::Label,
}
impl SimpleComponent for AppModel {
type Input = AppInput;
type Output = ();
type Init = u8;
type Root = gtk::Window;
type Widgets = AppWidgets;
fn init_root() -> Self::Root {
gtk::Window::builder()
.title("Simple App")
.default_width(300)
.default_height(100)
.build()
}
fn init(
counter: Self::Init,
window: &Self::Root,
sender: ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = AppModel { counter };
let vbox = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(5)
.build();
let inc_button = gtk::Button::with_label("Increment");
let dec_button = gtk::Button::with_label("Decrement");
let label = gtk::Label::new(Some(&format!("Counter: {}", model.counter)));
label.set_margin_all(5);
window.set_child(Some(&vbox));
vbox.set_margin_all(5);
vbox.append(&inc_button);
vbox.append(&dec_button);
vbox.append(&label);
inc_button.connect_clicked(clone!(@strong sender => move |_| {
sender.input(AppInput::Increment);
}));
dec_button.connect_clicked(clone!(@strong sender => move |_| {
sender.input(AppInput::Decrement);
}));
let widgets = AppWidgets { label };
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
match message {
AppInput::Increment => {
self.counter = self.counter.wrapping_add(1);
}
AppInput::Decrement => {
self.counter = self.counter.wrapping_sub(1);
}
}
}
fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>) {
widgets.label.set_label(&format!("Counter: {}", self.counter));
}
}
fn main() {
let app = RelmApp::new("relm4.test.simple_manual");
app.run::<AppModel>(0);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
[package]
name = "counter_factory"
version = "0.1.0"
edition = "2021"
[dependencies]
relm4 = { version = "0.7.0-beta.2" }

View File

@@ -0,0 +1,91 @@
use gtk::prelude::{BoxExt, ButtonExt, OrientableExt};
use relm4::factory::{DynamicIndex, FactoryComponent, FactorySender};
use relm4::gtk;
#[derive(Debug)]
pub struct Counter {
value: u8,
}
#[derive(Debug)]
pub enum CounterMsg {
Increment,
Decrement,
}
#[derive(Debug)]
pub enum CounterOutput {
SendFront(DynamicIndex),
MoveUp(DynamicIndex),
MoveDown(DynamicIndex),
}
#[relm4::factory(pub)]
impl FactoryComponent for Counter {
type Init = u8;
type Input = CounterMsg;
type Output = CounterOutput;
type CommandOutput = ();
type ParentWidget = gtk::Box;
view! {
root = gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 10,
#[name(label)]
gtk::Label {
#[watch]
set_label: &self.value.to_string(),
set_width_chars: 3,
},
#[name(add_button)]
gtk::Button {
set_label: "+",
connect_clicked => CounterMsg::Increment,
},
#[name(remove_button)]
gtk::Button {
set_label: "-",
connect_clicked => CounterMsg::Decrement,
},
#[name(move_up_button)]
gtk::Button {
set_label: "Up",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::MoveUp(index.clone())).unwrap();
}
},
#[name(move_down_button)]
gtk::Button {
set_label: "Down",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::MoveDown(index.clone())).unwrap();
}
},
#[name(to_front_button)]
gtk::Button {
set_label: "To Start",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::SendFront(index.clone())).unwrap();
}
}
}
}
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
Self { value }
}
fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {
match msg {
CounterMsg::Increment => self.value = self.value.wrapping_add(1),
CounterMsg::Decrement => self.value = self.value.wrapping_sub(1),
}
}
}

View File

@@ -0,0 +1,107 @@
mod counter;
use counter::{Counter, CounterOutput};
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
use relm4::{gtk, factory::{FactoryVecDeque, DynamicIndex}, RelmApp, ComponentParts, ComponentSender, SimpleComponent};
struct App {
created_widgets: u8,
counters: FactoryVecDeque<Counter>,
}
#[derive(Debug)]
pub enum AppMsg {
AddCounter,
RemoveCounter,
SendFront(DynamicIndex),
MoveUp(DynamicIndex),
MoveDown(DynamicIndex),
}
#[relm4::component]
impl SimpleComponent for App {
type Init = u8;
type Input = AppMsg;
type Output = ();
view! {
gtk::Window {
set_title: Some("Factory Example"),
set_default_size: (300, 100),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
gtk::Button {
set_label: "Add Counter",
connect_clicked => AppMsg::AddCounter,
},
gtk::Button {
set_label: "Remove Counter",
connect_clicked => AppMsg::RemoveCounter,
},
#[local_ref]
counter_box -> gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
}
}
}
}
fn init(counter: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let counters = FactoryVecDeque::builder()
.launch_default()
.forward(sender.input_sender(), |msg| match msg {
CounterOutput::SendFront(index) => AppMsg::SendFront(index),
CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
});
let model = App {
created_widgets: counter,
counters,
};
let counter_box = model.counters.widget();
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
let mut counters_guard = self.counters.guard();
match msg {
AppMsg::AddCounter => {
counters_guard.push_back(self.created_widgets);
self.created_widgets = self.created_widgets.wrapping_add(1);
}
AppMsg::RemoveCounter => {
counters_guard.pop_back();
}
AppMsg::SendFront(index) => {
counters_guard.move_front(index.current_index());
}
AppMsg::MoveDown(index) => {
let index = index.current_index();
let new_index = index + 1;
if new_index < counters_guard.len() {
counters_guard.move_to(index, new_index);
}
}
AppMsg::MoveUp(index) => {
let index = index.current_index();
if index != 0 {
counters_guard.move_to(index, index - 1);
}
}
}
}
}
fn main() {
let app = RelmApp::new("relm4.example.factory");
app.run::<App>(0);
}

View File

@@ -0,0 +1,81 @@
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
struct CounterModel {
counter: u8,
}
#[derive(Debug)]
enum CounterMessage {
Increment,
Decrement,
}
#[relm4::component]
impl SimpleComponent for CounterModel {
type Init = u8;
type Input = CounterMessage;
type Output = ();
view! {
gtk::Window {
set_title: Some("Counter"),
set_default_width: 300,
set_default_height: 100,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_all: 5,
gtk::Button {
set_label: "Increment",
connect_clicked[sender] => move |_| {
sender.input(CounterMessage::Increment);
}
},
gtk::Button::with_label("Decrement") {
connect_clicked[sender] => move |_| {
sender.input(CounterMessage::Decrement);
}
},
gtk::Label {
#[watch]
set_label: &format!("Counter: {}", model.counter),
set_margin_all: 5,
},
if model.counter % 2 == 0 {
gtk::Label {
set_label: "The value is even",
}
} else {
gtk::Label {
set_label: "The value is odd",
}
}
}
}
}
fn init(counter: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let model = CounterModel { counter };
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
match msg {
CounterMessage::Increment => self.counter = self.counter.wrapping_add(1),
CounterMessage::Decrement => self.counter = self.counter.wrapping_sub(1),
}
}
}
fn main() {
let app = RelmApp::new("relm4.test.simple");
app.run::<CounterModel>(0);
}

View File

@@ -0,0 +1,55 @@
use gtk::prelude::*;
use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, SimpleComponent};
struct PanedModel {}
#[derive(Debug)]
enum PanedMessage {}
#[relm4::component]
impl SimpleComponent for PanedModel {
type Init = ();
type Input = PanedMessage;
type Output = ();
view! {
gtk::Window {
set_title: Some("Paned Example"),
set_default_width: 300,
set_default_height: 200,
gtk::Paned {
set_orientation: gtk::Orientation::Horizontal,
set_resize_start_child: false,
set_shrink_start_child: false,
set_resize_end_child: true,
#[wrap(Some)]
set_start_child = &gtk::Label {
set_label: "Left Side",
set_size_request: (100, -1)
},
#[wrap(Some)]
set_end_child = &gtk::Label {
set_label: "Right Side"
}
}
}
}
fn init(_: Self::Init, root: &Self::Root, _sender: ComponentSender<Self>) -> ComponentParts<Self> {
let model = PanedModel {};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, _msg: Self::Input, _sender: ComponentSender<Self>) {
// No update logic needed for this static UI
}
}
fn main() {
let app = RelmApp::new("relm4.test.paned");
app.run::<PanedModel>(());
}

22
relm4_example/src/main.rs Normal file
View File

@@ -0,0 +1,22 @@
use relm4::gtk::{Application, ApplicationWindow, prelude::{ApplicationExt, GtkWindowExt, ButtonExt, WidgetExt, ApplicationExtManual}};
use tgbutton::CustomButton;
mod tgbutton;
fn main() {
let application = Application::new(Some("com.example.custombutton"), Default::default());
application.connect_activate(|app| {
let window = ApplicationWindow::new(app);
window.set_title(Some("Custom Button Example"));
window.set_default_size(300, 100);
let custom_button = CustomButton::new();
custom_button.set_label("Custom Button");
window.set_child(Some(&custom_button));
window.show();
});
application.run();
}

View File

@@ -0,0 +1,61 @@
use relm4::gtk;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::Button;
mod imp {
use super::*;
#[derive(Default)]
pub struct CustomButton;
#[glib::object_subclass]
impl ObjectSubclass for CustomButton {
const NAME: &'static str = "CustomButton";
type Type = super::CustomButton;
type ParentType = Button;
}
impl ObjectImpl for CustomButton {}
impl WidgetImpl for CustomButton {
fn snapshot(&self, widget: &Self::Type, snapshot: &gtk::Snapshot) {
self.parent_snapshot(widget, snapshot);
let rect = widget.allocation();
let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, rect.width(), rect.height())
.expect("Can't create surface");
let cr = cairo::Context::new(&surface).expect("Can't create context");
// Draw the hollow circle
cr.set_source_rgb(1.0, 0.0, 0.0); // Red color
cr.set_line_width(5.0); // Line width for the circle
cr.arc(
(rect.width() / 2) as f64,
(rect.height() / 2) as f64,
22.5, // Radius
0.0,
2.0 * std::f64::consts::PI,
);
cr.stroke();
// Draw on the widget
let snapshot_rect = gtk::gdk::Rectangle::new(rect.x(), rect.y(), rect.width(), rect.height());
snapshot.append_cairo(&snapshot_rect, &surface);
}
}
impl ButtonImpl for CustomButton {}
}
glib::wrapper! {
pub struct CustomButton(ObjectSubclass<imp::CustomButton>)
@extends Button, gtk::Widget;
}
impl CustomButton {
pub fn new() -> Self {
glib::Object::new(&[]).expect("Failed to create CustomButton")
}
}
pub use imp::CustomButton;