QML UI: Nicer MediaPlayer UI, generic blocker wall

This commit is contained in:
Thomas Perl 2011-07-26 15:31:21 +02:00
parent 2f9be802e6
commit 98fdf50ee8
11 changed files with 180 additions and 114 deletions

View File

@ -13,16 +13,6 @@ Item {
signal close
signal response(int index)
MouseArea {
anchors.fill: parent
}
Rectangle {
color: "black"
anchors.fill: parent
opacity: .9
}
ListView {
visible: !contextMenuArea.subscribeMode
model: contextMenuArea.items

View File

@ -154,14 +154,49 @@ Rectangle {
leftMargin: Config.largeSpacing * 2
rightMargin: Config.largeSpacing * 2
topMargin: Config.largeSpacing * 2
bottomMargin: Config.largeSpacing * 2
bottomMargin: Config.largeSpacing * 2 + (nowPlayingThrobber.shouldAppear?nowPlayingThrobber.height:0)
}
Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
Behavior on scale { NumberAnimation { duration: Config.slowTransition; easing.type: Easing.InSine } }
}
Item {
id: overlayInteractionBlockWall
anchors.fill: parent
z: nowPlayingThrobber.opened?2:0
opacity: (nowPlayingThrobber.opened || contextMenu.state == 'opened' || messageDialog.opacity == 1)?1:0
Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
MouseArea {
anchors.fill: parent
onClicked: {
if (contextMenu.state == 'opened') {
contextMenu.close()
} else if (messageDialog.opacity == 1) {
messageDialog.opacity = 0
} else {
nowPlayingThrobber.opened = false
}
}
}
Rectangle {
anchors.fill: parent
color: 'black'
opacity: .7
}
Image {
anchors.fill: parent
source: 'artwork/mask.png'
}
}
NowPlayingThrobber {
z: 3
property bool shouldAppear: ((contextMenu.state != 'opened') && (mediaPlayer.episode !== undefined))
id: nowPlayingThrobber
@ -181,11 +216,12 @@ Rectangle {
id: mediaPlayer
visible: nowPlayingThrobber.opened
z: 3
anchors.top: parent.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: nowPlayingThrobber.opened?-100:0
anchors.topMargin: nowPlayingThrobber.opened?-height:0
Behavior on anchors.topMargin { PropertyAnimation { duration: Config.slowTransition } }
}
@ -348,11 +384,6 @@ Rectangle {
Behavior on opacity { PropertyAnimation { } }
Rectangle {
anchors.fill: parent
color: '#ee000000'
}
Text {
id: messageDialogText
anchors.centerIn: parent
@ -360,11 +391,6 @@ Rectangle {
font.pixelSize: 20
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: messageDialog.opacity = 0
}
}
}

View File

@ -3,10 +3,13 @@ import Qt 4.7
import QtMultimediaKit 1.1
import 'config.js' as Config
import 'util.js' as Util
Item {
id: mediaPlayer
height: (Config.largeSpacing * 2) + (150 * Config.scale)
property variant episode: undefined
property bool playing: audioPlayer.playing && !audioPlayer.paused
@ -52,18 +55,91 @@ Item {
anchors.fill: mediaPlayer
color: 'black'
Row {
spacing: Config.largeSpacing
anchors {
leftMargin: Config.largeSpacing
topMargin: Config.largeSpacing
left: parent.left
top: parent.top
}
Image {
id: coverArt
source: (episode!==undefined)?Util.formatCoverURL(episode.qpodcast):''
width: 150 * Config.scale
height: 150 * Config.scale
MouseArea {
anchors.fill: parent
onClicked: mediaPlayer.togglePlayback(episode)
}
sourceSize.width: width
sourceSize.height: height
}
Column {
id: textColumn
spacing: Config.smallSpacing
Item { height: 1; width: 1 }
Text {
text: episode.qtitle
color: 'white'
font.pixelSize: 30 * Config.scale
}
Text {
text: episode.qpodcast.qtitle
color: '#aaa'
font.pixelSize: 20 * Config.scale
}
}
}
PlaybackBar {
id: playbackBar
width: mediaPlayer.width - coverArt.width - 3*Config.largeSpacing
x: coverArt.width + 2*Config.largeSpacing
anchors.bottom: parent.bottom
anchors.bottomMargin: Config.largeSpacing
Behavior on opacity { PropertyAnimation { } }
progress: episode != undefined?(episode.qduration?(episode.qposition / episode.qduration):0):0
duration: episode != undefined?episode.qduration:0
onSetProgress: {
audioPlayer.setPosition(progress)
audioPlayer.paused = false
}
onForward: {
if (episode != undefined && episode.qduration > 0) {
var pos = (episode.qposition + 60)/episode.qduration
audioPlayer.setPosition(pos)
}
}
onBackward: {
if (episode != undefined && episode.qduration > 0) {
var pos = (episode.qposition - 60)/episode.qduration
if (pos < 0) pos = 0
audioPlayer.setPosition(pos)
}
}
}
Audio {
id: audioPlayer
property bool seekLater: false
/*onPlayingChanged: {
if (!playing) {
playing = true
position = 0
paused = true
}
}*/
onPositionChanged: {
episode.qposition = position/1000
}
@ -90,44 +166,6 @@ Item {
audioPlayer.position = position*episode.qduration*1000
}
}
PlaybackBar {
id: playbackBar
Behavior on opacity { PropertyAnimation { } }
progress: episode != undefined?(episode.qduration?(episode.qposition / episode.qduration):0):0
duration: episode != undefined?episode.qduration:0
paused: audioPlayer.paused
onSetProgress: {
audioPlayer.setPosition(progress)
audioPlayer.paused = false
}
onForward: {
if (episode != undefined && episode.qduration > 0) {
var pos = (episode.qposition + 60)/episode.qduration
audioPlayer.setPosition(pos)
}
}
onBackward: {
if (episode != undefined && episode.qduration > 0) {
var pos = (episode.qposition - 60)/episode.qduration
if (pos < 0) pos = 0
audioPlayer.setPosition(pos)
}
}
onSetPaused: {
audioPlayer.paused = !audioPlayer.paused
}
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
bottomMargin: Config.largeSpacing
leftMargin: Config.largeSpacing * 2
rightMargin: Config.largeSpacing * 2
}
}
}
}

View File

@ -11,7 +11,9 @@ Item {
signal clicked
height: Config.headerHeight
width: icon.width + text.width
width: icon.width + (opened?0:text.width)
Behavior on width { NumberAnimation { duration: Config.slowTransition } }
MouseArea {
anchors.fill: parent
@ -59,7 +61,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
color: 'white'
font.pixelSize: 20 * Config.scale
text: nowPlayingThrobber.caption
text: nowPlayingThrobber.opened?'':nowPlayingThrobber.caption
}
}
}

View File

@ -5,10 +5,8 @@ import 'config.js' as Config
Item {
id: root
property real progress: 0
property bool paused: false
property int duration: 0
signal setProgress(real progress)
signal setPaused()
signal forward()
signal backward()
@ -21,33 +19,6 @@ Item {
opacity: .8
}
PlaybackBarButton {
id: play
source: 'artwork/btn_play.png'
anchors.left: parent.left
states: [
State {
name: 'play'
PropertyChanges {
target: play
source: 'artwork/btn_play.png'
}
},
State {
name: 'pause'
PropertyChanges {
target: play
source: 'artwork/btn_pause.png'
}
}
]
state: root.paused?'play':'pause'
onClicked: root.setPaused()
}
PlaybackBarButton {
id: rwnd
anchors.left: play.right

View File

@ -2,6 +2,7 @@
import Qt 4.7
import 'config.js' as Config
import 'util.js' as Util
SelectableItem {
id: podcastItem
@ -50,7 +51,7 @@ SelectableItem {
}
Image {
source: (modelData.qcoverurl != '')?('image://cover/'+escape(modelData.qcoverfile)+'|'+escape(modelData.qcoverurl)+'|'+escape(modelData.qurl)):''
source: Util.formatCoverURL(modelData)
asynchronous: true
width: parent.width * .85
height: parent.height * .85

View File

@ -10,3 +10,16 @@ function formatDuration(duration) {
return hh + ms
}
function formatCoverURL(podcast) {
var cover_file = podcast.qcoverfile
var cover_url = podcast.qcoverurl
var podcast_url = podcast.qurl
if (cover_url == '') {
return ''
}
return ('image://cover/' + escape(cover_file) + '|' +
escape(cover_url) + '|' + escape(podcast_url))
}

View File

@ -1192,12 +1192,15 @@ class Model(object):
def podcast_sort_key(cls, podcast):
return cls.PodcastClass.sort_key(podcast)
@staticmethod
def sort_episodes_by_pubdate(episodes, reverse=False):
@classmethod
def episode_sort_key(cls, episode):
return episode.published
@classmethod
def sort_episodes_by_pubdate(cls, episodes, reverse=False):
"""Sort a list of PodcastEpisode objects chronologically
Returns a iterable, sorted sequence of the episodes
"""
get_key = lambda e: e.published
return sorted(episodes, key=get_key, reverse=reverse)
return sorted(episodes, key=cls.episode_sort_key, reverse=reverse)

View File

@ -418,8 +418,16 @@ class qtPodder(QObject):
return model.QEpisode(self, podcast, episode)
def select_podcast(self, podcast):
wrap = functools.partial(self.wrap_episode, podcast)
self.episode_model.set_objects(map(wrap, podcast.get_all_episodes()))
if isinstance(podcast, model.QPodcast):
# Normal QPodcast instance
wrap = functools.partial(self.wrap_episode, podcast)
objects = podcast.get_all_episodes()
else:
# EpisodeSubsetView
wrap = lambda args: self.wrap_episode(*args)
objects = podcast.get_all_episodes_with_podcast()
self.episode_model.set_objects(map(wrap, objects))
self.main.state = 'episodes'
def save_pending_data(self):

View File

@ -38,8 +38,9 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
self._cache = {}
def requestImage(self, id, size, requestedSize):
if id in self._cache:
return self._cache[id]
key = (id, requestedSize)
if key in self._cache:
return self._cache[key]
filename, cover_url, url = (urllib.unquote(x) for x in id.split('|'))
@ -69,8 +70,8 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
if image.isNull():
return image
else:
self._cache[id] = image.scaled(requestedSize, \
self._cache[key] = image.scaled(requestedSize, \
Qt.KeepAspectRatioByExpanding, \
Qt.SmoothTransformation)
return self._cache[id]
return self._cache[key]

View File

@ -90,10 +90,10 @@ class QEpisode(QObject):
never_changed = Signal()
source_url_changed = Signal()
def _id(self):
return self._episode.id
def _podcast(self):
return self._podcast
qid = Property(int, _id, notify=never_changed)
qpodcast = Property(QObject, _podcast, notify=never_changed)
def _title(self):
return convert(self._episode.title)
@ -317,6 +317,19 @@ class EpisodeSubsetView(QObject):
self.description = description
self.eql = eql
def get_all_episodes_with_podcast(self):
episodes = [(podcast, episode) for podcast in
self.podcast_list_model.get_podcasts()
for episode in podcast.get_all_episodes()]
# FIXME: Filter using EQL
def sort_key(pair):
podcast, episode = pair
return model.Model.episode_sort_key(episode)
return sorted(episodes, key=sort_key, reverse=True)
def get_all_episodes(self):
episodes = []
for podcast in self.podcast_list_model.get_podcasts():