mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-20 11:35:48 +00:00
Merge branch 'main' into feat/stardust
This commit is contained in:
commit
c2969b512f
8 changed files with 546 additions and 133 deletions
|
@ -24,11 +24,11 @@ impl Display for Encoder {
|
|||
}
|
||||
|
||||
impl Encoder {
|
||||
pub fn iter() -> Iter<'static, Encoder> {
|
||||
pub fn iter() -> Iter<'static, Self> {
|
||||
[Self::X264, Self::Nvenc, Self::Vaapi].iter()
|
||||
}
|
||||
|
||||
pub fn as_vec() -> Vec<Encoder> {
|
||||
pub fn as_vec() -> Vec<Self> {
|
||||
vec![Self::X264, Self::Nvenc, Self::Vaapi]
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,32 @@ impl Encoder {
|
|||
pub enum Codec {
|
||||
H264,
|
||||
H265,
|
||||
Avc,
|
||||
Hevc,
|
||||
}
|
||||
|
||||
impl Display for Codec {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::H264 => "h264",
|
||||
Self::H265 => "h265",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Codec {
|
||||
pub fn iter() -> Iter<'static, Self> {
|
||||
[Self::H264, Self::H265].iter()
|
||||
}
|
||||
|
||||
pub fn as_vec() -> Vec<Self> {
|
||||
vec![Self::H264, Self::H265]
|
||||
}
|
||||
|
||||
pub fn as_number(&self) -> u32 {
|
||||
match self {
|
||||
Self::H264 => 0,
|
||||
Self::H265 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -64,6 +88,19 @@ pub struct WivrnConfEncoder {
|
|||
pub group: Option<i32>,
|
||||
}
|
||||
|
||||
impl Default for WivrnConfEncoder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
encoder: Encoder::X264,
|
||||
codec: Codec::H264,
|
||||
bitrate: None,
|
||||
width: None,
|
||||
height: None,
|
||||
group: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WivrnConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
|
@ -9,6 +9,7 @@ use super::job_worker::JobWorker;
|
|||
use super::libsurvive_setup_window::LibsurviveSetupWindow;
|
||||
use super::main_view::MainViewMsg;
|
||||
use super::util::open_with_default_handler;
|
||||
use super::wivrn_conf_editor::{WivrnConfEditor, WivrnConfEditorInit, WivrnConfEditorMsg};
|
||||
use crate::builders::build_basalt::get_build_basalt_jobs;
|
||||
use crate::builders::build_libsurvive::get_build_libsurvive_jobs;
|
||||
use crate::builders::build_mercury::get_build_mercury_job;
|
||||
|
@ -102,6 +103,9 @@ pub struct App {
|
|||
fbt_config_editor: Option<Controller<FbtConfigEditor>>,
|
||||
#[tracker::do_not_track]
|
||||
libmonado: Option<libmonado_rs::Monado>,
|
||||
|
||||
#[tracker::do_not_track]
|
||||
wivrn_conf_editor: Option<Controller<WivrnConfEditor>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -128,6 +132,7 @@ pub enum Msg {
|
|||
ConfigFbt,
|
||||
DebugOpenPrefix,
|
||||
DebugOpenData,
|
||||
OpenWivrnConfig,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -632,6 +637,15 @@ impl SimpleComponent for App {
|
|||
self.get_selected_profile().prefix
|
||||
));
|
||||
}
|
||||
Msg::OpenWivrnConfig => {
|
||||
let editor = WivrnConfEditor::builder()
|
||||
.launch(WivrnConfEditorInit {
|
||||
root_win: self.app_win.clone().upcast::<gtk::Window>(),
|
||||
})
|
||||
.detach();
|
||||
editor.emit(WivrnConfEditorMsg::Present);
|
||||
self.wivrn_conf_editor = Some(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -669,6 +683,7 @@ impl SimpleComponent for App {
|
|||
}
|
||||
|
||||
let mut model = App {
|
||||
tracker: 0,
|
||||
application: init.application,
|
||||
app_win: root.clone(),
|
||||
inhibit_id: None,
|
||||
|
@ -687,6 +702,7 @@ impl SimpleComponent for App {
|
|||
MainViewOutMsg::DeleteProfile => Msg::DeleteProfile,
|
||||
MainViewOutMsg::SaveProfile(p) => Msg::SaveProfile(p),
|
||||
MainViewOutMsg::OpenLibsurviveSetup => Msg::OpenLibsurviveSetup,
|
||||
MainViewOutMsg::OpenWivrnConfig => Msg::OpenWivrnConfig,
|
||||
}),
|
||||
debug_view: DebugView::builder().launch(DebugViewInit {}).forward(
|
||||
sender.input_sender(),
|
||||
|
@ -712,7 +728,6 @@ impl SimpleComponent for App {
|
|||
setcap_confirm_dialog,
|
||||
enable_debug_view: config.debug_view_enabled,
|
||||
config,
|
||||
tracker: 0,
|
||||
profiles,
|
||||
xrservice_worker: None,
|
||||
build_worker: None,
|
||||
|
@ -720,6 +735,7 @@ impl SimpleComponent for App {
|
|||
fbt_config_editor: None,
|
||||
restart_xrservice: false,
|
||||
libmonado: None,
|
||||
wivrn_conf_editor: None,
|
||||
};
|
||||
let widgets = view_output!();
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod device_row_factory;
|
||||
pub mod env_var_row_factory;
|
||||
pub mod tracker_role_group_factory;
|
||||
pub mod wivrn_encoder_group_factory;
|
||||
|
|
196
src/ui/factories/wivrn_encoder_group_factory.rs
Normal file
196
src/ui/factories/wivrn_encoder_group_factory.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use crate::{
|
||||
file_builders::wivrn_config::{Codec, Encoder, WivrnConfEncoder},
|
||||
ui::{
|
||||
preference_rows::{combo_row, number_entry_row, spin_row},
|
||||
wivrn_conf_editor::WivrnConfEditorMsg,
|
||||
},
|
||||
};
|
||||
use relm4::{
|
||||
adw::prelude::*, factory::AsyncFactoryComponent, gtk::prelude::*, prelude::*,
|
||||
AsyncFactorySender,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WivrnEncoderModel {
|
||||
pub encoder_conf: WivrnConfEncoder,
|
||||
pub uid: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WivrnEncoderModelMsg {
|
||||
EncoderChanged(u32),
|
||||
CodecChanged(u32),
|
||||
BitrateChanged(Option<u32>),
|
||||
WidthChanged(Option<f32>),
|
||||
HeightChanged(Option<f32>),
|
||||
GroupChanged(Option<i32>),
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WivrnEncoderModelOutMsg {
|
||||
Delete(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WivrnEncoderModelInit {
|
||||
pub encoder_conf: Option<WivrnConfEncoder>,
|
||||
}
|
||||
|
||||
#[relm4::factory(async pub)]
|
||||
impl AsyncFactoryComponent for WivrnEncoderModel {
|
||||
type Init = WivrnEncoderModelInit;
|
||||
type Input = WivrnEncoderModelMsg;
|
||||
type Output = WivrnEncoderModelOutMsg;
|
||||
type CommandOutput = ();
|
||||
type ParentInput = WivrnConfEditorMsg;
|
||||
type ParentWidget = adw::PreferencesPage;
|
||||
|
||||
view! {
|
||||
root = adw::PreferencesGroup {
|
||||
set_title: "Encoder",
|
||||
#[wrap(Some)]
|
||||
set_header_suffix: delete_btn = >k::Button {
|
||||
set_label: "Delete",
|
||||
add_css_class: "destructive-action",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::Delete)
|
||||
},
|
||||
},
|
||||
add: encoder_combo = &combo_row(
|
||||
"Encoder",
|
||||
Some("x264: CPU based h264 encoding\nNVEnc: Nvidia GPU encoding\nVAAPI: Intel or AMD GPU encoding"),
|
||||
&self.encoder_conf.encoder.to_string(),
|
||||
Encoder::iter().map(Encoder::to_string).collect::<Vec<String>>(),
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |row| {
|
||||
sender.input(Self::Input::EncoderChanged(row.selected()));
|
||||
}
|
||||
}
|
||||
) -> adw::ComboRow,
|
||||
add: codec_combo = &combo_row(
|
||||
"Codec",
|
||||
None,
|
||||
&self.encoder_conf.codec.to_string(),
|
||||
Codec::iter().map(Codec::to_string).collect::<Vec<String>>(),
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |row| {
|
||||
sender.input(Self::Input::CodecChanged(row.selected()));
|
||||
}
|
||||
}
|
||||
) -> adw::ComboRow,
|
||||
add: bitrate_row = &number_entry_row(
|
||||
"Bitrate",
|
||||
&self.encoder_conf.bitrate
|
||||
.and_then(|n| Some(n.to_string()))
|
||||
.unwrap_or_default(),
|
||||
false,
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |row| {
|
||||
let txt = row.text();
|
||||
sender.input(Self::Input::BitrateChanged(
|
||||
if txt.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(txt.parse::<u32>().unwrap())
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
) -> adw::EntryRow,
|
||||
add: width_row = &spin_row(
|
||||
"Width",
|
||||
None,
|
||||
self.encoder_conf.width.unwrap_or(1.0).into(),
|
||||
0.0,
|
||||
1.0,
|
||||
0.01,
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |adj| {
|
||||
sender.input(Self::Input::WidthChanged(
|
||||
Some(adj.value() as f32)
|
||||
));
|
||||
}
|
||||
}
|
||||
) -> adw::SpinRow,
|
||||
add: height_row = &spin_row(
|
||||
"Height",
|
||||
None,
|
||||
self.encoder_conf.height.unwrap_or(1.0).into(),
|
||||
0.0,
|
||||
1.0,
|
||||
0.01,
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |adj| {
|
||||
sender.input(Self::Input::HeightChanged(
|
||||
Some(adj.value() as f32)
|
||||
));
|
||||
}
|
||||
}
|
||||
) -> adw::SpinRow,
|
||||
add: group_row = &spin_row(
|
||||
"Group",
|
||||
None,
|
||||
self.encoder_conf.group.unwrap_or(0).into(),
|
||||
0.0,
|
||||
99999.0,
|
||||
1.0,
|
||||
{
|
||||
let sender = sender.clone();
|
||||
move |adj| {
|
||||
sender.input(Self::Input::GroupChanged(
|
||||
Some(adj.value().trunc() as i32)
|
||||
));
|
||||
}
|
||||
}
|
||||
) -> adw::SpinRow,
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(&mut self, message: Self::Input, sender: AsyncFactorySender<Self>) {
|
||||
match message {
|
||||
Self::Input::EncoderChanged(idx) => {
|
||||
self.encoder_conf.encoder = Encoder::as_vec().get(idx as usize).unwrap().clone();
|
||||
}
|
||||
Self::Input::CodecChanged(idx) => {
|
||||
self.encoder_conf.codec = Codec::as_vec().get(idx as usize).unwrap().clone();
|
||||
}
|
||||
Self::Input::BitrateChanged(val) => {
|
||||
self.encoder_conf.bitrate = val;
|
||||
}
|
||||
Self::Input::WidthChanged(val) => {
|
||||
self.encoder_conf.width = val;
|
||||
}
|
||||
Self::Input::HeightChanged(val) => {
|
||||
self.encoder_conf.height = val;
|
||||
}
|
||||
Self::Input::GroupChanged(val) => {
|
||||
self.encoder_conf.group = val;
|
||||
}
|
||||
Self::Input::Delete => sender.output(Self::Output::Delete(self.uid.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||
Some(match output {
|
||||
Self::Output::Delete(id) => Self::ParentInput::DeleteEncoder(id),
|
||||
})
|
||||
}
|
||||
|
||||
async fn init_model(
|
||||
init: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: AsyncFactorySender<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
encoder_conf: init.encoder_conf.unwrap_or_default(),
|
||||
uid: Uuid::new_v4().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ use crate::gpu_profile::{
|
|||
get_amd_gpu_power_profile, get_first_amd_gpu, get_set_amd_vr_pow_prof_cmd, GpuPowerProfile,
|
||||
GpuSysDrm,
|
||||
};
|
||||
use crate::profile::{LighthouseDriver, Profile};
|
||||
use crate::profile::{LighthouseDriver, Profile, XRServiceType};
|
||||
use crate::steamvr_utils::chaperone_info_exists;
|
||||
use crate::ui::app::{
|
||||
AboutAction, BuildProfileAction, BuildProfileCleanAction, BuildProfileCleanDebugAction,
|
||||
|
@ -87,6 +87,7 @@ pub enum MainViewOutMsg {
|
|||
DeleteProfile,
|
||||
SaveProfile(Profile),
|
||||
OpenLibsurviveSetup,
|
||||
OpenWivrnConfig,
|
||||
}
|
||||
|
||||
pub struct MainViewInit {
|
||||
|
@ -343,6 +344,32 @@ impl SimpleComponent for MainView {
|
|||
|
||||
model.steam_launch_options_box.widget(),
|
||||
model.install_wivrn_box.widget(),
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_vexpand: false,
|
||||
set_spacing: 12,
|
||||
add_css_class: "card",
|
||||
add_css_class: "padded",
|
||||
#[track = "model.changed(Self::selected_profile())"]
|
||||
set_visible: model.selected_profile.xrservice_type == XRServiceType::Wivrn,
|
||||
gtk::Label {
|
||||
add_css_class: "heading",
|
||||
set_hexpand: true,
|
||||
set_xalign: 0.0,
|
||||
set_label: "Configure WiVRn",
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_label: "Configure",
|
||||
set_halign: gtk::Align::End,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.output(Self::Output::OpenWivrnConfig).expect("Sender output failed");
|
||||
}
|
||||
},
|
||||
},
|
||||
model.steamvr_calibration_box.widget(),
|
||||
|
||||
gtk::Box {
|
||||
|
@ -383,26 +410,6 @@ impl SimpleComponent for MainView {
|
|||
}
|
||||
},
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "dim-label",
|
||||
set_hexpand: true,
|
||||
set_label: concat!(
|
||||
"Libsurvive needs to import your SteamVR calibration to work ",
|
||||
"properly. You need to have used SteamVR with this setup ",
|
||||
"before to be able to import its calibration."
|
||||
),
|
||||
set_xalign: 0.0,
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_label: "Calibrate",
|
||||
set_halign: gtk::Align::Start,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.output(Self::Output::OpenLibsurviveSetup).expect("Sender output failed");
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
} -> {
|
||||
|
|
|
@ -60,6 +60,102 @@ pub fn entry_row<F: Fn(&adw::EntryRow) + 'static>(
|
|||
row
|
||||
}
|
||||
|
||||
fn is_int(t: &str) -> bool {
|
||||
t.find(|c: char| !c.is_digit(10)).is_none()
|
||||
}
|
||||
|
||||
fn convert_to_int(t: &str) -> String {
|
||||
t.trim().chars().filter(|c| c.is_digit(10)).collect()
|
||||
}
|
||||
|
||||
fn is_float(t: &str) -> bool {
|
||||
let mut has_dot = false;
|
||||
for c in t.chars() {
|
||||
if c == '.' {
|
||||
if has_dot {
|
||||
return false;
|
||||
}
|
||||
has_dot = true;
|
||||
} else if !c.is_digit(10) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn convert_to_float(t: &str) -> String {
|
||||
let mut s = String::new();
|
||||
let mut has_dot = false;
|
||||
for c in t.trim().chars() {
|
||||
if c.is_digit(10) {
|
||||
s.push(c);
|
||||
} else if c == '.' && !has_dot {
|
||||
s.push(c);
|
||||
has_dot = true;
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub fn number_entry_row<F: Fn(&adw::EntryRow) + 'static>(
|
||||
title: &str,
|
||||
value: &str,
|
||||
float: bool,
|
||||
cb: F,
|
||||
) -> adw::EntryRow {
|
||||
let validator = if float { is_float } else { is_int };
|
||||
let converter = if float {
|
||||
convert_to_float
|
||||
} else {
|
||||
convert_to_int
|
||||
};
|
||||
let row = entry_row(title, value, move |row| {
|
||||
let txt_gstr = row.text();
|
||||
let txt = txt_gstr.as_str();
|
||||
if validator(txt) {
|
||||
cb(row)
|
||||
} else {
|
||||
row.set_text(&converter(txt));
|
||||
}
|
||||
});
|
||||
row.set_input_purpose(gtk::InputPurpose::Number);
|
||||
row
|
||||
}
|
||||
|
||||
pub fn spin_row<F: Fn(>k::Adjustment) + 'static>(
|
||||
title: &str,
|
||||
subtitle: Option<&str>,
|
||||
val: f64,
|
||||
min: f64,
|
||||
max: f64,
|
||||
min_increment: f64,
|
||||
cb: F,
|
||||
) -> adw::SpinRow {
|
||||
let adj = gtk::Adjustment::builder()
|
||||
.lower(min)
|
||||
.upper(max)
|
||||
.step_increment(min_increment)
|
||||
.page_increment(min_increment)
|
||||
.value(val)
|
||||
.build();
|
||||
let row = adw::SpinRow::builder()
|
||||
.title(title)
|
||||
.adjustment(&adj)
|
||||
.digits(if min_increment == 1.0 {
|
||||
0
|
||||
} else {
|
||||
min_increment.to_string().len() - 2
|
||||
} as u32)
|
||||
.build();
|
||||
if let Some(sub) = subtitle {
|
||||
row.set_subtitle(sub);
|
||||
}
|
||||
|
||||
adj.connect_value_changed(cb);
|
||||
|
||||
row
|
||||
}
|
||||
|
||||
pub fn path_row<F: Fn(Option<String>) + 'static + Clone>(
|
||||
title: &str,
|
||||
description: Option<&str>,
|
||||
|
@ -147,3 +243,33 @@ pub fn combo_row<F: Fn(&adw::ComboRow) + 'static>(
|
|||
|
||||
row
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::preference_rows::{convert_to_float, is_float};
|
||||
|
||||
#[test]
|
||||
fn accepts_float() {
|
||||
assert!(is_float("132.1425"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_float_with_many_dots() {
|
||||
assert!(!is_float("132.142.5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_float_without_dots() {
|
||||
assert!(is_float("1321425"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_float_with_alphas() {
|
||||
assert!(!is_float("123.34a65"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_to_float() {
|
||||
assert_eq!(convert_to_float("123a.435.123"), "123.435123");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ impl SimpleComponent for ProfileEditor {
|
|||
set_vexpand: true,
|
||||
add_top_bar: top_bar = &adw::HeaderBar {
|
||||
set_vexpand: false,
|
||||
set_show_end_title_buttons: false,
|
||||
set_show_start_title_buttons: false,
|
||||
pack_end: save_btn = >k::Button {
|
||||
set_label: "Save",
|
||||
add_css_class: "suggested-action",
|
||||
|
@ -73,6 +75,13 @@ impl SimpleComponent for ProfileEditor {
|
|||
sender.input(Self::Input::SaveProfile);
|
||||
},
|
||||
},
|
||||
pack_start: cancel_btn = >k::Button {
|
||||
set_label: "Cancel",
|
||||
add_css_class: "destructive-action",
|
||||
connect_clicked[win] => move |_| {
|
||||
win.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_content: pref_page = &adw::PreferencesPage {
|
||||
|
|
|
@ -1,28 +1,30 @@
|
|||
use crate::file_builders::wivrn_config::{
|
||||
dump_wivrn_config, get_wivrn_config, Encoder, WivrnConfig,
|
||||
use super::factories::wivrn_encoder_group_factory::{WivrnEncoderModel, WivrnEncoderModelInit};
|
||||
use crate::{
|
||||
file_builders::wivrn_config::{dump_wivrn_config, get_wivrn_config, WivrnConfig},
|
||||
ui::preference_rows::spin_row,
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use relm4::prelude::*;
|
||||
use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
|
||||
|
||||
#[tracker::track]
|
||||
pub struct WivrnConfEditor {
|
||||
conf: WivrnConfig,
|
||||
#[tracker::do_not_track]
|
||||
win: Option<adw::PreferencesWindow>,
|
||||
win: Option<adw::Window>,
|
||||
#[tracker::do_not_track]
|
||||
scalex_entry: Option<adw::EntryRow>,
|
||||
pub encoder_models: Option<AsyncFactoryVecDeque<WivrnEncoderModel>>,
|
||||
#[tracker::do_not_track]
|
||||
scaley_entry: Option<adw::EntryRow>,
|
||||
pub scalex_row: Option<adw::SpinRow>,
|
||||
#[tracker::do_not_track]
|
||||
encoder_combo: Option<adw::ComboRow>,
|
||||
#[tracker::do_not_track]
|
||||
bitrate_entry: Option<adw::EntryRow>,
|
||||
pub scaley_row: Option<adw::SpinRow>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WivrnConfEditorMsg {
|
||||
Present,
|
||||
Save,
|
||||
AddEncoder,
|
||||
DeleteEncoder(String),
|
||||
}
|
||||
|
||||
pub struct WivrnConfEditorInit {
|
||||
|
@ -37,81 +39,86 @@ impl SimpleComponent for WivrnConfEditor {
|
|||
|
||||
view! {
|
||||
#[name(win)]
|
||||
adw::PreferencesWindow {
|
||||
set_hide_on_close: true,
|
||||
adw::Window {
|
||||
set_modal: true,
|
||||
set_transient_for: Some(&init.root_win),
|
||||
set_title: Some("WiVRn Configuration"),
|
||||
add: mainpage = &adw::PreferencesPage {
|
||||
add: scalegrp = &adw::PreferencesGroup {
|
||||
set_title: "Scale",
|
||||
set_description: Some("Render resolution scale. 1.0 is 100%."),
|
||||
#[name(scalex_entry)]
|
||||
adw::EntryRow {
|
||||
set_title: "Scale X",
|
||||
#[track = "model.changed(Self::conf())"]
|
||||
set_text: match model.conf.scale {
|
||||
Some([x, _]) => x.to_string(),
|
||||
None => "".to_string(),
|
||||
}.as_str(),
|
||||
set_input_purpose: gtk::InputPurpose::Number,
|
||||
set_default_height: 500,
|
||||
set_default_width: 600,
|
||||
adw::ToolbarView {
|
||||
set_top_bar_style: adw::ToolbarStyle::Flat,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
add_top_bar: top_bar = &adw::HeaderBar {
|
||||
set_vexpand: false,
|
||||
set_show_end_title_buttons: false,
|
||||
set_show_start_title_buttons: false,
|
||||
pack_end: save_btn = >k::Button {
|
||||
set_label: "Save",
|
||||
add_css_class: "suggested-action",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::Save);
|
||||
},
|
||||
},
|
||||
#[name(scaley_entry)]
|
||||
adw::EntryRow {
|
||||
set_title: "Scale Y",
|
||||
#[track = "model.changed(Self::conf())"]
|
||||
set_text: match model.conf.scale {
|
||||
Some([_, y]) => y.to_string(),
|
||||
None => "".to_string(),
|
||||
}.as_str(),
|
||||
set_input_purpose: gtk::InputPurpose::Number,
|
||||
pack_start: cancel_btn = >k::Button {
|
||||
set_label: "Cancel",
|
||||
add_css_class: "destructive-action",
|
||||
connect_clicked[win] => move |_| {
|
||||
win.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
add: encgrp = &adw::PreferencesGroup {
|
||||
set_title: "Encoder",
|
||||
#[name(encoder_combo)]
|
||||
adw::ComboRow {
|
||||
set_title: "Encoder",
|
||||
set_subtitle: "x264: CPU based h264 encoding\n\nNVEnc: Nvidia GPU encoding\n\nVAAPI: Intel or AMD GPU encoding",
|
||||
set_model: Some(>k::StringList::new(
|
||||
Encoder::iter()
|
||||
.map(Encoder::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice()
|
||||
)),
|
||||
#[track = "model.changed(Self::conf())"]
|
||||
set_selected: model.conf.encoders.get(0).unwrap().encoder.as_number(),
|
||||
#[wrap(Some)]
|
||||
set_content: pref_page = &adw::PreferencesPage {
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
set_description: "<a href=\"https://github.com/Meumeu/WiVRn#encoders\">WiVRn Configuration Documentation</a>",
|
||||
add: scalegrp = &adw::PreferencesGroup {
|
||||
set_title: "Scale",
|
||||
set_description: Some("Render resolution scale. 1.0 is 100%."),
|
||||
add: scalex_row = &spin_row(
|
||||
"Scale X",
|
||||
None,
|
||||
match model.conf.scale {
|
||||
Some([x, _]) => x.into(),
|
||||
None => 1.0,
|
||||
},
|
||||
0.0,
|
||||
1.0,
|
||||
0.01,
|
||||
move |_| {}
|
||||
) -> adw::SpinRow,
|
||||
add: scaley_row = &spin_row(
|
||||
"Scale Y",
|
||||
None,
|
||||
match model.conf.scale {
|
||||
Some([_, y]) => y.into(),
|
||||
None => 1.0,
|
||||
},
|
||||
0.0,
|
||||
1.0,
|
||||
0.01,
|
||||
move |_| {}
|
||||
) -> adw::SpinRow,
|
||||
},
|
||||
#[name(bitrate_entry)]
|
||||
adw::EntryRow {
|
||||
set_title: "Bitrate",
|
||||
#[track = "model.changed(Self::conf())"]
|
||||
set_text: match model.conf.encoders.get(0).unwrap().bitrate {
|
||||
Some(br) => br.to_string(),
|
||||
None => "".to_string()
|
||||
}.as_str(),
|
||||
set_input_purpose: gtk::InputPurpose::Number,
|
||||
},
|
||||
},
|
||||
add: save_grp = &adw::PreferencesGroup {
|
||||
add: save_box = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_hexpand: true,
|
||||
gtk::Button {
|
||||
set_halign: gtk::Align::Center,
|
||||
set_label: "Save",
|
||||
add_css_class: "pill",
|
||||
add_css_class: "suggested-action",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::Save);
|
||||
add: encodersrgp = &adw::PreferencesGroup {
|
||||
set_title: "Encoders",
|
||||
adw::ActionRow {
|
||||
set_title: "Add encoder",
|
||||
add_suffix: add_encoder_btn = >k::Button {
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Center,
|
||||
add_css_class: "suggested-action",
|
||||
set_icon_name: "list-add-symbolic",
|
||||
set_tooltip_text: Some("Add encoder"),
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::AddEncoder)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,34 +131,41 @@ impl SimpleComponent for WivrnConfEditor {
|
|||
self.win.as_ref().unwrap().present();
|
||||
}
|
||||
Self::Input::Save => {
|
||||
let scalex = self.scalex_entry.as_ref().unwrap().text().parse::<f32>();
|
||||
let scaley = self.scaley_entry.as_ref().unwrap().text().parse::<f32>();
|
||||
if scalex.is_ok() && scaley.is_ok() {
|
||||
self.conf.scale = Some([*scalex.as_ref().unwrap(), *scaley.as_ref().unwrap()]);
|
||||
}
|
||||
if scalex.is_ok() || scaley.is_ok() {
|
||||
let scale = scalex.unwrap_or(scaley.unwrap());
|
||||
self.conf.scale = Some([scale, scale]);
|
||||
} else {
|
||||
self.conf.scale = None
|
||||
}
|
||||
|
||||
let mut enc = self.conf.encoders.remove(0);
|
||||
let bitrate = self.bitrate_entry.as_ref().unwrap().text().parse::<u32>();
|
||||
if let Ok(br) = bitrate {
|
||||
enc.bitrate = Some(br);
|
||||
}
|
||||
let encoders = Encoder::as_vec();
|
||||
let encoder =
|
||||
encoders.get(self.encoder_combo.as_ref().unwrap().selected() as usize);
|
||||
if let Some(e) = encoder {
|
||||
enc.encoder = e.clone();
|
||||
}
|
||||
self.conf.encoders.insert(0, enc);
|
||||
let x = self.scalex_row.as_ref().unwrap().adjustment().value();
|
||||
let y = self.scaley_row.as_ref().unwrap().adjustment().value();
|
||||
Some([x as f32, y as f32]);
|
||||
|
||||
self.conf.encoders = self
|
||||
.encoder_models
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(Option::is_some)
|
||||
.map(|m| m.as_ref().unwrap().encoder_conf.clone())
|
||||
.collect();
|
||||
dump_wivrn_config(&self.conf);
|
||||
self.win.as_ref().unwrap().close();
|
||||
}
|
||||
Self::Input::AddEncoder => {
|
||||
self.encoder_models
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.guard()
|
||||
.push_back(WivrnEncoderModelInit::default());
|
||||
}
|
||||
Self::Input::DeleteEncoder(id) => {
|
||||
let idx_opt = self
|
||||
.encoder_models
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.position(|m_opt| m_opt.is_some_and(|m| m.uid == id));
|
||||
if let Some(idx) = idx_opt {
|
||||
self.encoder_models.as_mut().unwrap().guard().remove(idx);
|
||||
} else {
|
||||
eprintln!("Couldn't find encoder model with id {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,21 +176,28 @@ impl SimpleComponent for WivrnConfEditor {
|
|||
) -> ComponentParts<Self> {
|
||||
let mut model = Self {
|
||||
conf: get_wivrn_config(),
|
||||
encoder_models: None,
|
||||
win: None,
|
||||
scalex_entry: None,
|
||||
scaley_entry: None,
|
||||
bitrate_entry: None,
|
||||
encoder_combo: None,
|
||||
scalex_row: None,
|
||||
scaley_row: None,
|
||||
tracker: 0,
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
model.scalex_row = Some(widgets.scalex_row.clone());
|
||||
model.scaley_row = Some(widgets.scaley_row.clone());
|
||||
|
||||
let mut encoder_models: AsyncFactoryVecDeque<WivrnEncoderModel> =
|
||||
AsyncFactoryVecDeque::new(widgets.pref_page.clone(), sender.input_sender());
|
||||
for encoder_conf in model.conf.encoders.clone() {
|
||||
encoder_models.guard().push_back(WivrnEncoderModelInit {
|
||||
encoder_conf: Some(encoder_conf),
|
||||
});
|
||||
}
|
||||
model.encoder_models = Some(encoder_models);
|
||||
|
||||
model.win = Some(widgets.win.clone());
|
||||
model.scalex_entry = Some(widgets.scalex_entry.clone());
|
||||
model.scaley_entry = Some(widgets.scaley_entry.clone());
|
||||
model.bitrate_entry = Some(widgets.bitrate_entry.clone());
|
||||
model.encoder_combo = Some(widgets.encoder_combo.clone());
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue