Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
de981eafe4 | |||
45c6548c40 | |||
84fd4b9262 | |||
a792c79015 | |||
7839160141 | |||
f42ef80bdd | |||
b41e31b4be | |||
3d89e51418 | |||
cb6beb71d8 | |||
66722e8313 | |||
1598329342 | |||
2c671f6228 | |||
d151a73b97 | |||
ffd5eebfd2 | |||
b89ab394ff |
2237
Cargo.lock
generated
2237
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,4 +6,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
rusb = "0.9.4"
|
rusb = "0.9.4"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
cacao = "0.3.2"
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
async-channel = "2.3.1"
|
||||||
|
gtk = { version = "0.9.5", package = "gtk4", features = ["v4_16"] }
|
||||||
|
soundcloud = { git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Abandoned. Take a look at [Lyrica](https://gitea.awain.net/alterwain/Lyrica)
|
114
src/main.rs
114
src/main.rs
@ -1,41 +1,21 @@
|
|||||||
use cacao::appkit::window::{TitleVisibility, Window, WindowConfig, WindowController};
|
use gtk::{prelude::*, ApplicationWindow, Orientation, Stack, StackSidebar};
|
||||||
use cacao::appkit::{App, AppDelegate};
|
use gtk::{glib, Application};
|
||||||
use cacao::button::Button;
|
use glib::clone;
|
||||||
use cacao::view::{SplitViewController, View, ViewDelegate};
|
|
||||||
|
|
||||||
use crate::view::details_view::Details;
|
use std::{sync::OnceLock, error::Error};
|
||||||
use crate::view::sidebar::MainSidebar;
|
use tokio::runtime::Runtime;
|
||||||
use crate::view::content_view::ScreenView;
|
|
||||||
use crate::window::main_window::MainWindow;
|
use soundcloud::sobjects::CloudPlaylists;
|
||||||
|
|
||||||
mod disk_util;
|
mod disk_util;
|
||||||
mod ipod_util;
|
mod ipod_util;
|
||||||
|
|
||||||
mod view;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
const VENDOR_ID: u16 = 1452;
|
const VENDOR_ID: u16 = 1452;
|
||||||
const PRODUCT_ID: u16 = 4617;
|
const PRODUCT_ID: u16 = 4617;
|
||||||
|
|
||||||
struct ILoaderApp {
|
const APP_ID: &str = "com.alterdekim.iloader";
|
||||||
window: WindowController<MainWindow>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppDelegate for ILoaderApp {
|
fn main() -> glib::ExitCode {
|
||||||
/// There should be stuff which loads underlying logic for communication with IPod
|
|
||||||
fn will_finish_launching(&self) {}
|
|
||||||
|
|
||||||
fn did_finish_launching(&self) {
|
|
||||||
App::activate();
|
|
||||||
self.window.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_terminate_after_last_window_closed(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
/*for device in rusb::devices().unwrap().iter() {
|
/*for device in rusb::devices().unwrap().iter() {
|
||||||
let device_desc = device.device_descriptor().unwrap();
|
let device_desc = device.device_descriptor().unwrap();
|
||||||
if VENDOR_ID == device_desc.vendor_id() && PRODUCT_ID == device_desc.product_id() {
|
if VENDOR_ID == device_desc.vendor_id() && PRODUCT_ID == device_desc.product_id() {
|
||||||
@ -43,9 +23,77 @@ fn main() {
|
|||||||
println!("{}", ipod_util::get_ipod_path().is_some());
|
println!("{}", ipod_util::get_ipod_path().is_some());
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
let config = WindowConfig::default();
|
// Create a new application
|
||||||
|
let app = Application::builder().application_id(APP_ID).build();
|
||||||
|
|
||||||
App::new("com.alterdekim.iloader", ILoaderApp {
|
app.connect_activate(build_ui);
|
||||||
window: WindowController::with(config, MainWindow::default())
|
|
||||||
}).run();
|
// Run the application
|
||||||
|
app.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime() -> &'static Runtime {
|
||||||
|
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
|
||||||
|
RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_ui(app: &Application) {
|
||||||
|
let (sender, receiver) = async_channel::bounded::<CloudPlaylists>(1);
|
||||||
|
runtime().spawn(clone!(
|
||||||
|
#[strong]
|
||||||
|
sender,
|
||||||
|
async move {
|
||||||
|
let app_version = soundcloud::get_app().await.unwrap().unwrap();
|
||||||
|
let client_id = soundcloud::get_client_id().await.unwrap().unwrap();
|
||||||
|
let user_id: u64 = 774639751;
|
||||||
|
|
||||||
|
sender
|
||||||
|
.send(soundcloud::get_playlists(user_id, client_id, app_version).await.unwrap())
|
||||||
|
.await
|
||||||
|
.expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
let window = ApplicationWindow::builder()
|
||||||
|
.application(app)
|
||||||
|
.title("ILoader")
|
||||||
|
.default_width(980)
|
||||||
|
.default_height(700)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let hbox = gtk::Box::new(Orientation::Horizontal, 5);
|
||||||
|
|
||||||
|
let stack = Stack::new();
|
||||||
|
stack.set_transition_type(gtk::StackTransitionType::SlideLeftRight); // Add some pages to the stack
|
||||||
|
|
||||||
|
let label1 = gtk::Label::new(Some("Youtube Content"));
|
||||||
|
stack.add_titled(&label1, Some("page1"), "Youtube");
|
||||||
|
|
||||||
|
let grid_layout = gtk::Grid::builder()
|
||||||
|
.row_spacing(5)
|
||||||
|
.column_spacing(5)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
window.set_child(Some(&hbox));
|
||||||
|
|
||||||
|
let label3 = gtk::Label::new(Some("Spotify Content"));
|
||||||
|
stack.add_titled(&label3, Some("page3"), "Spotify");
|
||||||
|
|
||||||
|
let sidebar = StackSidebar::new();
|
||||||
|
sidebar.set_stack(&stack);
|
||||||
|
|
||||||
|
hbox.append(&sidebar);
|
||||||
|
hbox.append(&stack);
|
||||||
|
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
while let Ok(response) = receiver.recv().await {
|
||||||
|
for playlist in response.collection {
|
||||||
|
let label2 = gtk::Label::new(Some(&playlist.title));
|
||||||
|
grid_layout.attach(&label2, 0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
stack.add_titled(&grid_layout, Some("page2"), "Soundcloud");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.present();
|
||||||
}
|
}
|
41
src/theme.rs
Normal file
41
src/theme.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use iced::theme::Palette;
|
||||||
|
use iced::widget::button::{Status, Style};
|
||||||
|
use iced::{Background, Border, Color, Font, Shadow, Vector};
|
||||||
|
use iced::Length::Fill;
|
||||||
|
use iced::{widget::container, window, Theme, Element, Settings, Task as Command};
|
||||||
|
use iced::widget::{button, column, pick_list, radio, text, Column, Container, scrollable};
|
||||||
|
|
||||||
|
pub const SF_FONT: iced::Font = Font {
|
||||||
|
family: iced::font::Family::Name("SF Pro Text"),
|
||||||
|
weight: iced::font::Weight::Normal,
|
||||||
|
stretch: iced::font::Stretch::Normal,
|
||||||
|
style: iced::font::Style::Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get_default_theme() -> Theme {
|
||||||
|
Theme::custom("Glossy".to_string(), Palette {
|
||||||
|
background: Color::from_rgba8(255, 255, 255, 1.0), // Color::from_rgba8(244, 245, 245, 1.0)
|
||||||
|
text: Color::from_rgb8(0, 0, 0),
|
||||||
|
primary: Color::from_rgb8(0, 122, 255),
|
||||||
|
success: Color::from_rgb8(52, 199, 89),
|
||||||
|
danger: Color::from_rgb8(255, 59, 48),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn basic_button_theme(theme: &Theme, _status: Status) -> Style {
|
||||||
|
Style {
|
||||||
|
background: Some(Background::Color(Color::from_rgb8(255, 255, 255))),
|
||||||
|
text_color: Color::from_rgb8(0, 0, 0), // theme.palette().text
|
||||||
|
border: Border {
|
||||||
|
color: Color::TRANSPARENT,
|
||||||
|
width: 0.0,
|
||||||
|
radius: 5.0.into(),
|
||||||
|
},
|
||||||
|
shadow: Shadow {
|
||||||
|
color: Color::from_rgba8(0, 0, 0, 0.1),
|
||||||
|
offset: Vector::new(0.0, 1.0),
|
||||||
|
blur_radius: 0.1,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
60
src/view.rs
60
src/view.rs
@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
pub mod sidebar {
|
|
||||||
use cacao::{appkit::FocusRingType, button::{BezelStyle, Button}, geometry::Rect, layout::Layout, text::Font, view::{View, ViewDelegate}};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MainSidebar {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewDelegate for MainSidebar {
|
|
||||||
const NAME: &'static str = "MainSidebar";
|
|
||||||
|
|
||||||
fn did_load(&mut self, view: View) {
|
|
||||||
let mut btn = Button::new("testtesttest");
|
|
||||||
btn.set_bezel_style(BezelStyle::TexturedRounded);
|
|
||||||
btn.set_bordered(false);
|
|
||||||
btn.set_font(Font::system(14.));
|
|
||||||
btn.set_action(|| {
|
|
||||||
println!("HEY");
|
|
||||||
});
|
|
||||||
view.add_subview(&btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod content_view {
|
|
||||||
use cacao::{button::Button, layout::Layout, view::{View, ViewDelegate}};
|
|
||||||
|
|
||||||
pub struct ScreenView {
|
|
||||||
pub btn: Button
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ScreenView {
|
|
||||||
fn default() -> Self {
|
|
||||||
let btn = Button::new("test");
|
|
||||||
Self { btn }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewDelegate for ScreenView {
|
|
||||||
const NAME: &'static str = "ScreenView";
|
|
||||||
|
|
||||||
fn did_load(&mut self, view: View) {
|
|
||||||
view.add_subview(&self.btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod details_view {
|
|
||||||
use cacao::{button::Button, layout::Layout, view::{View, ViewDelegate}};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Details {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewDelegate for Details {
|
|
||||||
const NAME: &'static str = "Details";
|
|
||||||
}
|
|
||||||
}
|
|
142
src/widget.rs
Normal file
142
src/widget.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use iced::{widget::{button, column, container, row, text, Column}, Element, Length::Fill, Padding, Task as Command};
|
||||||
|
|
||||||
|
use crate::{theme, Message};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn basic_btn(s: String) -> button::Button<'static, Message> {
|
||||||
|
button(container(text(s).center().font(theme::SF_FONT).size(13.0).line_height(1.)).padding(Padding {
|
||||||
|
top: 1.5,
|
||||||
|
right: 2.,
|
||||||
|
bottom: 1.5,
|
||||||
|
left: 2.,
|
||||||
|
})).style(theme::basic_button_theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value T should be something, that inherits ActionWindow
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SidebarTab {
|
||||||
|
Youtube(String),
|
||||||
|
Spotify(String),
|
||||||
|
Soundcloud(String),
|
||||||
|
ITunes(String),
|
||||||
|
Playlists(String),
|
||||||
|
FileSystem(String),
|
||||||
|
Metadata(String),
|
||||||
|
FindCopies(String),
|
||||||
|
Settings(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for SidebarTab {
|
||||||
|
fn into(self) -> String {
|
||||||
|
match self {
|
||||||
|
SidebarTab::Youtube(a) => a,
|
||||||
|
SidebarTab::Spotify(a) => a,
|
||||||
|
SidebarTab::Soundcloud(a) => a,
|
||||||
|
SidebarTab::ITunes(a) => a,
|
||||||
|
SidebarTab::Playlists(a) => a,
|
||||||
|
SidebarTab::FileSystem(a) => a,
|
||||||
|
SidebarTab::Metadata(a) => a,
|
||||||
|
SidebarTab::FindCopies(a) => a,
|
||||||
|
SidebarTab::Settings(a) => a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SidebarGroup {
|
||||||
|
tabs: Vec<(SidebarTab, Box<dyn ActionWindow>)>,
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidebarGroup {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
Self{tabs: Vec::new(), name}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_tab(&mut self, t: (SidebarTab, Box<dyn ActionWindow>)) {
|
||||||
|
self.tabs.push(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SidebarGroup> for Element<'_, Message> {
|
||||||
|
fn from(value: &SidebarGroup) -> Self {
|
||||||
|
container(column![
|
||||||
|
text(value.name.clone()),
|
||||||
|
column(
|
||||||
|
value.tabs
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
let s: String = i.0.clone().into();
|
||||||
|
basic_btn(s).into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<Message>>>()
|
||||||
|
)
|
||||||
|
]).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ActionWindow {
|
||||||
|
fn view(&self) -> Element<Message>;
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SettingsWindow {}
|
||||||
|
|
||||||
|
impl ActionWindow for SettingsWindow {
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct YTWindow {}
|
||||||
|
|
||||||
|
impl ActionWindow for YTWindow {
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SplitView {
|
||||||
|
sidebar: Vec<SidebarGroup>,
|
||||||
|
selected: Option<(SidebarTab, Box<dyn ActionWindow>)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplitView {
|
||||||
|
pub fn view(&self) -> Element<Message> {
|
||||||
|
row![
|
||||||
|
column(
|
||||||
|
self.sidebar.iter()
|
||||||
|
.map(|f| f.into())
|
||||||
|
.collect::<Vec<Element<Message>>>()
|
||||||
|
),
|
||||||
|
if self.selected.is_some() { container((&self.selected.as_ref().unwrap().1).view()) } else { container(text!("")) }
|
||||||
|
].into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
if self.selected.is_some() {
|
||||||
|
let a = &mut self.selected.as_mut().unwrap().1;
|
||||||
|
return a.update(message);
|
||||||
|
}
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_group(&mut self, group: SidebarGroup) {
|
||||||
|
self.sidebar.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self{ sidebar: Vec::new(), selected: None }
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
pub mod main_window {
|
|
||||||
use cacao::{appkit::window::{TitleVisibility, Window, WindowDelegate}, view::SplitViewController};
|
|
||||||
|
|
||||||
use crate::view::{content_view::ScreenView, details_view::Details, sidebar::MainSidebar};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MainWindow {
|
|
||||||
split_view_controller: Option<SplitViewController<MainSidebar, ScreenView, Details>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowDelegate for MainWindow {
|
|
||||||
const NAME: &'static str = "MainWindow";
|
|
||||||
|
|
||||||
fn did_load(&mut self, window: Window) {
|
|
||||||
window.set_title("ILoader");
|
|
||||||
window.set_title_visibility(TitleVisibility::Hidden);
|
|
||||||
window.set_titlebar_appears_transparent(true);
|
|
||||||
window.set_movable_by_background(true);
|
|
||||||
window.set_autosave_name("CacaoILoader");
|
|
||||||
window.set_minimum_content_size(980., 700.);
|
|
||||||
|
|
||||||
let split_view_controller = SplitViewController::new(MainSidebar::default(),
|
|
||||||
ScreenView::default(),
|
|
||||||
Some(Details::default())); // Some(DetailView::default())
|
|
||||||
|
|
||||||
window.set_content_view_controller(&split_view_controller);
|
|
||||||
|
|
||||||
self.split_view_controller = Some(split_view_controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user