diff --git a/.vscode/settings.json b/.vscode/settings.json index a108acb..2cdd41a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,6 +58,17 @@ "forward_list": "cpp", "iomanip": "cpp", "sstream": "cpp", - "valarray": "cpp" + "valarray": "cpp", + "complex": "cpp", + "bitset": "cpp", + "condition_variable": "cpp", + "set": "cpp", + "mutex": "cpp", + "semaphore": "cpp", + "stop_token": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "typeindex": "cpp", + "variant": "cpp" } } \ No newline at end of file diff --git a/lib/gana/App.cpp b/lib/gana/App.cpp index 4c23c3f..1aa76e8 100644 --- a/lib/gana/App.cpp +++ b/lib/gana/App.cpp @@ -147,7 +147,8 @@ void App::set_focused_node(Node *node) node->set_focus(true); _focused_node = node; signal_node_focus.emit(_focused_node); - node->on_focus(); + if (node != nullptr) + node->on_focus(); } Node *App::get_focused_node() diff --git a/lib/gana/type/Event.cpp b/lib/gana/type/Event.cpp index 6382b88..00c2045 100644 --- a/lib/gana/type/Event.cpp +++ b/lib/gana/type/Event.cpp @@ -11,6 +11,11 @@ Event::~Event() {} bool Event::is_touch() const +{ + return (is_touch_up()); +} + +bool Event::is_touch_up() const { if ((type == sf::Event::MouseButtonReleased && mouseButton.button == sf::Mouse::Left) || type == sf::Event::TouchEnded) return (true); @@ -18,6 +23,14 @@ bool Event::is_touch() const return (false); } +bool Event::is_touch_down() const +{ + if ((type == sf::Event::MouseButtonPressed && mouseButton.button == sf::Mouse::Left) || type == sf::Event::TouchBegan) + return (true); + else + return (false); +} + bool Event::accept_pressed() const { if ((type == sf::Event::KeyPressed && key.code == sf::Keyboard::Enter) || (type == sf::Event::JoystickButtonReleased && joystickButton.button == SwitchPadButton::A)) @@ -34,4 +47,16 @@ bool Event::cancel_pressed() const return (false); } +Vector2f Event::get_position() const +{ + if (type == sf::Event::MouseButtonPressed || type == sf::Event::MouseButtonReleased) { + return (Vector2f(mouseButton.x, mouseButton.y)); + } else if (type == sf::Event::MouseMoved) { + return (Vector2f(mouseMove.x, mouseMove.y)); + } else if (type == sf::Event::TouchBegan || type == sf::Event::TouchEnded || type == sf::Event::TouchMoved) { + return (Vector2f(touch.x, touch.y)); + } + return (Vector2f()); +} + } diff --git a/lib/gana/type/Event.hpp b/lib/gana/type/Event.hpp index 8a079b1..967f595 100644 --- a/lib/gana/type/Event.hpp +++ b/lib/gana/type/Event.hpp @@ -3,6 +3,7 @@ #define EVENT_HPP_ #include +#include "Vector2.hpp" namespace gana { @@ -12,12 +13,17 @@ class Event: public sf::Event { ~Event(); bool handle; - // Return true if the event is a left click or a touch + // Return true if the event is a left click or a touch and key is released bool is_touch() const; + // Return true if the event is a left click or a touch and key is pressed + bool is_touch_down() const; + // Return true if the event is a left click or a touch and key is released + bool is_touch_up() const; // Switch button A is pressed or Enter key pressed bool accept_pressed() const; // Switch button B is pressed or Escape key pressed bool cancel_pressed() const; + Vector2f get_position() const; private: }; diff --git a/lib/gana/type/Vector2.hpp b/lib/gana/type/Vector2.hpp index 5843526..5874887 100644 --- a/lib/gana/type/Vector2.hpp +++ b/lib/gana/type/Vector2.hpp @@ -19,6 +19,7 @@ class Vector2 { Vector2 normalize(); float length(); + float distance(const Vector2 &point); std::string to_string() const; Vector2 &operator*(T nb); Vector2 operator*(const Vector2 &vec) const; diff --git a/lib/gana/type/Vector2.inl b/lib/gana/type/Vector2.inl index 5ac0769..f45fcba 100644 --- a/lib/gana/type/Vector2.inl +++ b/lib/gana/type/Vector2.inl @@ -27,17 +27,23 @@ inline Vector2 Vector2::normalize() } template -inline std::string Vector2::to_string() const +inline float Vector2::length() { - std::ostringstream str; - str << "Vector2(" << x << ", " << y << ")"; - return (str.str()); + return (std::sqrt(this->x * this->x + this->y * this->y)); } template -inline float Vector2::length() +inline float Vector2::distance(const Vector2 &point) { - return (std::sqrt(this->x * this->x + this->y * this->y)); + return (std::sqrt(std::pow(this->x - point.x, 2) + std::pow(this->y - point.y, 2))); +} + +template +inline std::string Vector2::to_string() const +{ + std::ostringstream str; + str << "Vector2(" << x << ", " << y << ")"; + return (str.str()); } template diff --git a/lib/gana/ui/MPVPlayer.cpp b/lib/gana/ui/MPVPlayer.cpp index cb3c708..a6e3522 100644 --- a/lib/gana/ui/MPVPlayer.cpp +++ b/lib/gana/ui/MPVPlayer.cpp @@ -67,6 +67,12 @@ bool MPVPlayer::is_seeking() return (seeking == 1); } +bool MPVPlayer::is_core_idle() +{ + int64_t core_idle; + mpv_get_property(_handle, "core-idle", MPV_FORMAT_INT64, &core_idle); + return (core_idle == 1); +} int64_t MPVPlayer::get_time_pos() { @@ -75,6 +81,11 @@ int64_t MPVPlayer::get_time_pos() return (data); } +void MPVPlayer::set_time_pos(int64_t pos) +{ + mpv_set_property(_handle, "time-pos", MPV_FORMAT_INT64, &pos); +} + int64_t MPVPlayer::get_duration() { int64_t data; @@ -156,6 +167,8 @@ void MPVPlayer::event() } } else { Logger::info("MPV event: %s", mpv_event_name(mp_event->event_id)); + if (mp_event->event_id == MPV_EVENT_FILE_LOADED) + signal_file_loaded.emit(); } } _mpv_event = false; diff --git a/lib/gana/ui/MPVPlayer.hpp b/lib/gana/ui/MPVPlayer.hpp index 90d0e22..4c330dc 100644 --- a/lib/gana/ui/MPVPlayer.hpp +++ b/lib/gana/ui/MPVPlayer.hpp @@ -5,6 +5,7 @@ #include #include #include "ui/Node.hpp" +#include "type/Signal.hpp" #define DEBUG_MPV 0 @@ -17,8 +18,12 @@ class MPVPlayer: public Node { void set_source(const std::string &src); bool is_seeking(); + bool is_core_idle(); int64_t get_time_pos(); + void set_time_pos(int64_t pos); int64_t get_duration(); + + Signal<> signal_file_loaded; protected: void process(); void enter_tree(); diff --git a/romfs/icon/pause-24.png b/romfs/icon/pause-24.png new file mode 100644 index 0000000..42bc298 Binary files /dev/null and b/romfs/icon/pause-24.png differ diff --git a/romfs/icon/pause-48.png b/romfs/icon/pause-48.png new file mode 100644 index 0000000..36644c2 Binary files /dev/null and b/romfs/icon/pause-48.png differ diff --git a/src/network/item/duration.cpp b/src/network/item/duration.cpp index d0dfa24..b02f09a 100644 --- a/src/network/item/duration.cpp +++ b/src/network/item/duration.cpp @@ -5,6 +5,8 @@ static const std::size_t TICK_PER_HOUR = 36000000000; static const std::size_t TICK_PER_MINUTE = 600000000; static const std::size_t TICK_PER_SECOND = 10000000; +static const std::size_t MPV_TICK_PER_HOUR = 3600; +static const std::size_t MPV_TICK_PER_MINUTE = 60; std::string tick_to_duration(Tick tick) { @@ -18,3 +20,16 @@ std::string tick_to_duration(Tick tick) str << minute << "m"; return (str.str()); } + +std::string mpv_tick_to_duration(uint64_t tick) +{ + std::ostringstream str; + int hour = tick / MPV_TICK_PER_HOUR; + int minute = tick / MPV_TICK_PER_MINUTE; + + if (hour > 0) { + str << hour << ":"; + } + str << minute << ":" << (tick % MPV_TICK_PER_MINUTE); + return (str.str()); +} \ No newline at end of file diff --git a/src/network/item/duration.hpp b/src/network/item/duration.hpp index 26b3ae5..5baa221 100644 --- a/src/network/item/duration.hpp +++ b/src/network/item/duration.hpp @@ -6,5 +6,6 @@ #include "item_type.hpp" std::string tick_to_duration(Tick tick); +std::string mpv_tick_to_duration(uint64_t tick); #endif /* DURATION_HPP_ */ \ No newline at end of file diff --git a/src/ui/player/Player.cpp b/src/ui/player/Player.cpp index 7b82ee6..39a37ef 100644 --- a/src/ui/player/Player.cpp +++ b/src/ui/player/Player.cpp @@ -1,11 +1,39 @@ +#include "network/item/duration.hpp" #include "Player.hpp" +static const float MIN_TIME_WIDTH = 100; + Player::Player(gana::NavigationManager &nav, std::shared_ptr client, const Item &item) { _player.set_source(client->get_stream_url(item.get_id())); + _player.signal_file_loaded.connect(*this, &Player::on_file_loaded); add_child(&_player); - set_process(); + + _ctn.add_spacer(8, true); + + _lbl_current_time.set_text("-"); + _lbl_current_time.set_min_size(gana::Vector2f(MIN_TIME_WIDTH, 0)); + _lbl_current_time.set_text_align(gana::BaseLabel::TextAlign::CENTER); + _ctn_duration_bar.add_child(&_lbl_current_time); + + _slider_bar.signal_value_changed.connect(*this, &Player::on_slider_value_changed); + _slider_bar.set_expand(); + _ctn_duration_bar.add_child(&_slider_bar); + + _lbl_duration.set_text("-"); + _lbl_duration.set_min_size(gana::Vector2f(MIN_TIME_WIDTH, 0)); + _lbl_duration.set_text_align(gana::BaseLabel::TextAlign::CENTER); + _ctn_duration_bar.add_child(&_lbl_duration); + + _ctn_duration_bar.set_hsizing(gana::Node::Sizing::FILL); + _ctn_duration_bar.set_margin(16, 0, 16, 0); + _ctn.add_child(&_ctn_duration_bar); + + _ctn.set_anchor(gana::Node::Anchor::FULL_RECT); + add_child(&_ctn); + + set_anchor(gana::Node::Anchor::FULL_RECT); } Player::~Player() @@ -13,5 +41,22 @@ Player::~Player() void Player::process() { - gana::Logger::info("time pos %d/%d", _player.get_time_pos(), _player.get_duration()); + uint64_t time = _player.get_time_pos(); + _slider_bar.set_value(time); + _lbl_current_time.set_text(mpv_tick_to_duration(time)); } + +void Player::on_file_loaded() +{ + set_process(); + uint64_t time = _player.get_time_pos(); + _slider_bar.set_max_value(_player.get_duration()); + _slider_bar.set_value(time); + _lbl_current_time.set_text(mpv_tick_to_duration(time)); + _lbl_duration.set_text(mpv_tick_to_duration(_player.get_duration())); +} + +void Player::on_slider_value_changed(uint value) +{ + _player.set_time_pos(value); +} \ No newline at end of file diff --git a/src/ui/player/Player.hpp b/src/ui/player/Player.hpp index 5d8b76d..3a833fc 100644 --- a/src/ui/player/Player.hpp +++ b/src/ui/player/Player.hpp @@ -6,7 +6,11 @@ #include "gana/ui/Node.hpp" #include "gana/ui/NavigationManager.hpp" #include "gana/ui/MPVPlayer.hpp" +#include "gana/ui/Label.hpp" +#include "gana/ui/box_container/VBoxContainer.hpp" +#include "gana/ui/box_container/HBoxContainer.hpp" #include "network/JellyfinClient.hpp" +#include "Slider.hpp" class Player: public gana::Node { public: @@ -16,7 +20,14 @@ class Player: public gana::Node { protected: void process(); private: + void on_file_loaded(); + void on_slider_value_changed(uint value); gana::MPVPlayer _player; + gana::VBoxContainer _ctn; + gana::HBoxContainer _ctn_duration_bar; + gana::Label _lbl_current_time; + gana::Label _lbl_duration; + Slider _slider_bar; }; #endif /* PLAYER_HPP_ */ \ No newline at end of file diff --git a/src/ui/player/Slider.cpp b/src/ui/player/Slider.cpp new file mode 100644 index 0000000..445b3dd --- /dev/null +++ b/src/ui/player/Slider.cpp @@ -0,0 +1,73 @@ + +#include "gana/theme/color.hpp" +#include "gana/App.hpp" +#include "Slider.hpp" + +static const gana::Vector2f MIN_SIZE = gana::Vector2f(20, 20); +static const int BAR_HEIGTH = 12; + +Slider::Slider(): _value(0), _max_value(0), _percentage(0) +{ + set_min_size(MIN_SIZE); +} + +Slider::~Slider() +{} + +void Slider::draw(NVGcontext *ctx) +{ + float y = get_draw_positon().y + (get_draw_size().y - BAR_HEIGTH) / 2; + nvgBeginPath(ctx); + nvgRoundedRect(ctx, get_draw_positon().x, y, get_draw_size().x, BAR_HEIGTH, BAR_HEIGTH / 2); + nvgFillColor(ctx, gana::Color(40, 40, 40, 225).nvg_color()); + nvgFill(ctx); + nvgBeginPath(ctx); + nvgRoundedRect(ctx, get_draw_positon().x, y, get_draw_size().x * _percentage, BAR_HEIGTH, BAR_HEIGTH / 2); + nvgFillColor(ctx, gana::theme::PRIMARY.nvg_color()); + nvgFill(ctx); + + nvgBeginPath(ctx); + nvgCircle(ctx, _center_sliding_point.x, _center_sliding_point.y, MIN_SIZE.y / 2); + nvgFillColor(ctx, gana::Color(255, 40, 40).nvg_color()); + nvgFill(ctx); +} + +void Slider::process_event(gana::Event &evt) +{ + if (evt.is_touch_down() && evt.get_position().distance(_center_sliding_point) < MIN_SIZE.y) { + _app->set_focused_node(this); + evt.handle = true; + } else if (has_focus() && (evt.type == sf::Event::TouchMoved || evt.type == sf::Event::MouseMoved)) { + if (evt.get_position().x > get_draw_positon().x && evt.get_position().x < get_draw_positon().x + get_draw_size().x) { + _percentage = (evt.get_position().x - get_draw_positon().x) / get_draw_size().x; + _value = _max_value * _percentage; + update_percentage(); + } + } else if (has_focus()) { + signal_value_changed.emit(_value); + _app->set_focused_node(nullptr); + } +} + +void Slider::set_max_value(uint value) +{ + if (has_focus()) + return; + _max_value = value; + update_percentage(); +} + +void Slider::set_value(uint value) +{ + if (has_focus()) + return; + _value = value; + update_percentage(); +} + +void Slider::update_percentage() +{ + _percentage = (float)_value / (float)_max_value; + _center_sliding_point.x = get_draw_positon().x + get_draw_size().x * _percentage; + _center_sliding_point.y = get_draw_positon().y + MIN_SIZE.y / 2; +} \ No newline at end of file diff --git a/src/ui/player/Slider.hpp b/src/ui/player/Slider.hpp new file mode 100644 index 0000000..9e2cfc7 --- /dev/null +++ b/src/ui/player/Slider.hpp @@ -0,0 +1,27 @@ + +#ifndef SLIDER_HPP_ +#define SLIDER_HPP_ + +#include "gana/ui/Node.hpp" +#include "gana/type/Signal.hpp" + +class Slider: public gana::Node { + public: + Slider(); + ~Slider(); + + gana::Signal signal_value_changed; + + void draw(NVGcontext *ctx) override; + void process_event(gana::Event &evt) override; + void set_max_value(uint value); + void set_value(uint value); + private: + void update_percentage(); + uint _value; + uint _max_value; + float _percentage; + gana::Vector2f _center_sliding_point; +}; + +#endif /* SLIDER_HPP_ */ \ No newline at end of file