// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* * Copyright 2012 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR * PURPOSE. See the applicable version of the GNU Lesser General Public * License for more details. * * You should have received a copy of both the GNU Lesser General Public * License version 3 along with this program. If not, see * * * Authored by: Nick Dedekind * */ #include "Track.h" #include "unity-shared/IntrospectableWrappers.h" #include #include #include #include #include #include namespace unity { namespace dash { namespace previews { namespace { const RawPixel LAYOUT_SPACING = 6_em; const RawPixel TITLE_PADDING = 3_em; } class TmpView : public nux::View { public: TmpView(NUX_FILE_LINE_PROTO): View(NUX_FILE_LINE_PARAM) {} virtual ~TmpView() {} virtual void Draw(nux::GraphicsEngine& gfx_engine, bool force_draw) {} virtual void DrawContent(nux::GraphicsEngine& gfx_engine, bool force_draw) { if (GetCompositionLayout()) GetCompositionLayout()->ProcessDraw(gfx_engine, force_draw); } virtual bool AcceptKeyNavFocus() { return false; } }; NUX_IMPLEMENT_OBJECT_TYPE(Track); class TrackProgressLayer : public nux::AbstractPaintLayer { public: TrackProgressLayer(nux::Color const& left_color, nux::Color const& right_color, nux::Color const& progress_color, bool write_alpha = false, nux::ROPConfig const& ROP = nux::ROPConfig::Default) : left_color_(left_color) , right_color_(right_color) , progress_color_(progress_color) , write_alpha_(write_alpha) , rop_(ROP) {} virtual ~TrackProgressLayer() {} virtual void Renderlayer(nux::GraphicsEngine& graphics_engine) { unsigned int current_red_mask; unsigned int current_green_mask; unsigned int current_blue_mask; unsigned int current_alpha_mask; unsigned int current_alpha_blend; unsigned int current_src_blend_factor; unsigned int current_dest_blend_factor; // Get the current color mask and blend states. They will be restored later. graphics_engine.GetRenderStates().GetColorMask(current_red_mask, current_green_mask, current_blue_mask, current_alpha_mask); // Get the current blend states. They will be restored later. graphics_engine.GetRenderStates().GetBlend(current_alpha_blend, current_src_blend_factor, current_dest_blend_factor); graphics_engine.GetRenderStates().SetColorMask(GL_TRUE, GL_TRUE, GL_TRUE, write_alpha_ ? GL_TRUE : GL_FALSE); graphics_engine.GetRenderStates().SetBlend(rop_.Blend, rop_.SrcBlend, rop_.DstBlend); // Gradient to current pos. graphics_engine.QRP_Color(geometry_.x, geometry_.y, geometry_.width, geometry_.height, left_color_, left_color_, right_color_, right_color_); int current_progress_pos = geometry_.width > 2 ? (geometry_.x + geometry_.width) - 2 : geometry_.x; // current pos outline. graphics_engine.QRP_Color(current_progress_pos, geometry_.y, MIN(2, geometry_.width), geometry_.height, progress_color_); // Restore Color mask and blend states. graphics_engine.GetRenderStates().SetColorMask(current_red_mask, current_green_mask, current_blue_mask, current_alpha_mask); // Restore the blend state graphics_engine.GetRenderStates().SetBlend(current_alpha_blend, current_src_blend_factor, current_dest_blend_factor); } virtual nux::AbstractPaintLayer* Clone() const { return new TrackProgressLayer(*this); } private: nux::Color left_color_; nux::Color right_color_; nux::Color progress_color_; bool write_alpha_; nux::ROPConfig rop_; }; Track::Track(NUX_FILE_LINE_DECL) : View(NUX_FILE_LINE_PARAM) , scale(1.0) , play_state_(PlayerState::STOPPED) , progress_(0.0) , mouse_over_(false) { SetupBackground(); SetupViews(); scale.changed.connect(sigc::mem_fun(this, &Track::UpdateScale)); } std::string Track::GetName() const { return "Track"; } void Track::AddProperties(debug::IntrospectionData& introspection) { introspection .add("uri", uri_) .add("play-state", (int)play_state_) .add("progress", progress_) .add("playpause-x", track_status_layout_->GetAbsoluteX()) .add("playpause-y", track_status_layout_->GetAbsoluteX()) .add("playpause-width", track_status_layout_->GetGeometry().width) .add("playpause-height", track_status_layout_->GetGeometry().height) .add("playpause-geo", track_status_layout_->GetAbsoluteGeometry()); } void Track::Update(dash::Track const& track) { uri_ = track.uri; title_->SetText(track.title, true); std::stringstream ss_track_number; ss_track_number << track.track_number; track_number_->SetText(ss_track_number.str()); glib::String duration(g_strdup_printf("%d:%.2d", track.length/60, (track.length) % 60)); duration_->SetText(duration); player_connection_ = player_.updated.connect([this](std::string const& uri, PlayerState player_state, double progress) { if (uri != uri_) { // If we're received an update for another track, we're obviously not playing this track anymore. if (progress_ != 0.0 || play_state_ != PlayerState::STOPPED) { progress_ = 0.0; play_state_ = PlayerState::STOPPED; UpdateTrackState(); } return; } progress_ = progress; play_state_ = player_state; UpdateTrackState(); }); QueueDraw(); } void Track::SetupBackground() { nux::ROPConfig rop; rop.Blend = true; rop.SrcBlend = GL_ONE; rop.DstBlend = GL_ONE_MINUS_SRC_ALPHA; focus_layer_.reset(new nux::ColorLayer(nux::Color(0.15f, 0.15f, 0.15f, 0.15f), true, rop)); progress_layer_.reset(new TrackProgressLayer(nux::Color(0.25f, 0.25f, 0.25f, 0.15f), nux::Color(0.05f, 0.05f, 0.05f, 0.15f), nux::Color(0.60f, 0.60f, 0.60f, 0.15f), true, rop)); } void Track::SetupViews() { previews::Style& style = previews::Style::Instance(); nux::HLayout* layout = new nux::HLayout(); layout->SetLeftAndRightPadding(0,0); nux::BaseTexture* tex_play = style.GetPlayIcon(); status_play_ = new IconTexture(tex_play, style.GetStatusIconSize().CP(scale), style.GetStatusIconSize().CP(scale)); status_play_->SetDrawMode(IconTexture::DrawMode::STRETCH_WITH_ASPECT); nux::BaseTexture* tex_pause = style.GetPauseIcon(); status_pause_ = new IconTexture(tex_pause, style.GetStatusIconSize().CP(scale), style.GetStatusIconSize().CP(scale)); status_pause_->SetDrawMode(IconTexture::DrawMode::STRETCH_WITH_ASPECT); track_number_ = new StaticCairoText("", NUX_TRACKER_LOCATION); track_number_->SetTextAlignment(StaticCairoText::NUX_ALIGN_CENTRE); track_number_->SetTextVerticalAlignment(StaticCairoText::NUX_ALIGN_CENTRE); track_number_->SetLines(-1); track_number_->SetScale(scale); track_number_->SetFont(style.track_font()); title_ = new StaticCairoText("", NUX_TRACKER_LOCATION); title_->SetTextAlignment(StaticCairoText::NUX_ALIGN_LEFT); title_->SetTextVerticalAlignment(StaticCairoText::NUX_ALIGN_CENTRE); title_->SetLines(-1); title_->SetScale(scale); title_->SetFont(style.track_font()); duration_ = new StaticCairoText("", NUX_TRACKER_LOCATION); duration_->SetTextEllipsize(StaticCairoText::NUX_ELLIPSIZE_NONE); duration_->SetTextAlignment(StaticCairoText::NUX_ALIGN_RIGHT); duration_->SetTextVerticalAlignment(StaticCairoText::NUX_ALIGN_CENTRE); duration_->SetLines(-1); duration_->SetMinimumWidth(style.GetMusicDurationWidth().CP(scale)); duration_->SetMaximumWidth(style.GetMusicDurationWidth().CP(scale)); duration_->SetScale(scale); duration_->SetFont(style.track_font()); // Layouts // stick text fields in a layout so they don't alter thier geometry. status_play_layout_ = new TmpView(); status_play_layout_->SetLayout(new nux::HLayout()); status_play_layout_->GetLayout()->AddSpace(0, 1); status_play_layout_->GetLayout()->AddView(status_play_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); status_play_layout_->GetLayout()->AddSpace(0, 1); status_pause_layout_ = new TmpView(); status_pause_layout_->SetLayout(new nux::HLayout()); status_pause_layout_->GetLayout()->AddSpace(0, 1); status_pause_layout_->GetLayout()->AddView(status_pause_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); status_pause_layout_->GetLayout()->AddSpace(0, 1); track_number_layout_ = new TmpView(); track_number_layout_->SetLayout(new nux::HLayout()); track_number_layout_->GetLayout()->AddSpace(0, 1); track_number_layout_->GetLayout()->AddView(track_number_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); track_number_layout_->GetLayout()->AddSpace(0, 1); track_status_layout_ = new nux::LayeredLayout(); track_status_layout_->AddLayer(status_play_layout_, true); track_status_layout_->AddLayer(status_pause_layout_, true); track_status_layout_->AddLayer(track_number_layout_, true); track_status_layout_->SetActiveLayer(track_number_layout_); title_layout_ = new nux::HLayout(); title_layout_->SetLeftAndRightPadding(TITLE_PADDING.CP(scale)); title_layout_->AddView(title_, 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); title_layout_->AddSpace(0, 0); duration_layout_ = new nux::HLayout(); duration_layout_->AddSpace(0, 1); duration_layout_->AddView(duration_, 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); layout->AddLayout(track_status_layout_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); layout->AddLayout(title_layout_, 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); layout->AddLayout(duration_layout_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); SetLayout(layout); mouse_enter.connect(sigc::mem_fun(this, &Track::OnTrackControlMouseEnter)); mouse_leave.connect(sigc::mem_fun(this, &Track::OnTrackControlMouseLeave)); mouse_click.connect([this](int, int, unsigned long, unsigned long) { switch (play_state_) { case PlayerState::PLAYING: player_.Pause(); break; case PlayerState::PAUSED: player_.Resume(); break; case PlayerState::STOPPED: default: player_.Play(uri_); break; } }); } void Track::Draw(nux::GraphicsEngine& gfx_engine, bool force_draw) { nux::Geometry const& base = GetGeometry(); unsigned int alpha, src, dest = 0; gfx_engine.GetRenderStates().GetBlend(alpha, src, dest); gfx_engine.GetRenderStates().SetBlend(true); if (HasStatusFocus()) { focus_layer_->SetGeometry(track_status_layout_->GetGeometry()); nux::GetPainter().RenderSinglePaintLayer(gfx_engine, focus_layer_->GetGeometry(), focus_layer_.get()); } int progress_width = progress_ * (duration_layout_->GetGeometry().x + duration_layout_->GetGeometry().width - title_layout_->GetGeometry().x); if (progress_width > 0.0) { nux::Geometry geo_progress(title_layout_->GetGeometry().x, base.y, progress_width, base.height); progress_layer_->SetGeometry(geo_progress); nux::GetPainter().RenderSinglePaintLayer(gfx_engine, progress_layer_->GetGeometry(), progress_layer_.get()); } gfx_engine.GetRenderStates().SetBlend(alpha, src, dest); } void Track::DrawContent(nux::GraphicsEngine& gfx_engine, bool force_draw) { nux::Geometry const& base = GetGeometry(); gfx_engine.PushClippingRectangle(base); int pop_layers = 0; if (!IsFullRedraw()) { if (HasStatusFocus()) { nux::GetPainter().PushLayer(gfx_engine, focus_layer_->GetGeometry(), focus_layer_.get()); pop_layers++; } int progress_width = progress_ * (duration_layout_->GetGeometry().x + duration_layout_->GetGeometry().width - title_layout_->GetGeometry().x); if (progress_width > 0.0) { nux::GetPainter().PushLayer(gfx_engine, progress_layer_->GetGeometry(), progress_layer_.get()); pop_layers++; } } else nux::GetPainter().PushPaintLayerStack(); unsigned int alpha, src, dest = 0; gfx_engine.GetRenderStates().GetBlend(alpha, src, dest); gfx_engine.GetRenderStates().SetBlend(true, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); if (GetCompositionLayout()) GetCompositionLayout()->ProcessDraw(gfx_engine, force_draw); gfx_engine.GetRenderStates().SetBlend(alpha, src, dest); if (IsFullRedraw()) nux::GetPainter().PopPaintLayerStack(); else if (pop_layers > 0) nux::GetPainter().PopBackground(pop_layers); gfx_engine.PopClippingRectangle(); } nux::Area* Track::FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type) { bool mouse_inside = TestMousePointerInclusion(mouse_position, event_type); if (mouse_inside == false) return NULL; return this; } bool Track::HasStatusFocus() const { return mouse_over_ || play_state_ == PlayerState::PLAYING || play_state_ == PlayerState::PAUSED; } void Track::OnTrackControlMouseEnter(int x, int y, unsigned long button_flags, unsigned long key_flags) { mouse_over_ = true; UpdateTrackState(); QueueDraw(); } void Track::OnTrackControlMouseLeave(int x, int y, unsigned long button_flags, unsigned long key_flags) { mouse_over_ = false; UpdateTrackState(); QueueDraw(); } void Track::UpdateTrackState() { if (mouse_over_) { switch (play_state_) { case PlayerState::PLAYING: track_status_layout_->SetActiveLayer(status_pause_layout_); break; case PlayerState::PAUSED: case PlayerState::STOPPED: default: track_status_layout_->SetActiveLayer(status_play_layout_); break; } } else { switch (play_state_) { case PlayerState::PLAYING: track_status_layout_->SetActiveLayer(status_play_layout_); break; case PlayerState::PAUSED: track_status_layout_->SetActiveLayer(status_pause_layout_); break; case PlayerState::STOPPED: default: track_status_layout_->SetActiveLayer(track_number_layout_); break; } } QueueDraw(); } void Track::PreLayoutManagement() { previews::Style& style = previews::Style::Instance(); nux::Geometry const& geo = GetGeometry(); track_status_layout_->SetMinimumWidth(geo.height); track_status_layout_->SetMaximumWidth(geo.height); const int max_width = std::max(GetGeometry().width - geo.height - style.GetMusicDurationWidth().CP(scale) - LAYOUT_SPACING.CP(scale)*2, 0); title_->SetMaximumWidth(max_width); View::PreLayoutManagement(); } void Track::UpdateScale(double scale) { auto& style = Style::Instance(); int icon_size = style.GetStatusIconSize().CP(scale); track_number_->SetScale(scale); title_->SetScale(scale); duration_->SetMaximumWidth(style.GetMusicDurationWidth().CP(scale)); duration_->SetMinimumWidth(style.GetMusicDurationWidth().CP(scale)); duration_->SetScale(scale); title_layout_->SetLeftAndRightPadding(TITLE_PADDING.CP(scale)); status_play_->SetMinMaxSize(icon_size, icon_size); status_pause_->SetMinMaxSize(icon_size, icon_size); QueueRelayout(); QueueDraw(); } } // namespace previews } // namespace dash } // namesapce unity