Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
deskscreen
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Package Registry
Container Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
kowis-projects
deskscreen
Commits
f11b4708
Commit
f11b4708
authored
Mar 18, 2019
by
David
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Can do the updates via an out-of-band observable!
parent
377113d5
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
58 additions
and
114 deletions
+58
-114
src/main/kotlin/is/kow/deskscreen/ticker/TickerView.kt
src/main/kotlin/is/kow/deskscreen/ticker/TickerView.kt
+58
-114
No files found.
src/main/kotlin/is/kow/deskscreen/ticker/TickerView.kt
View file @
f11b4708
package
`is`.kow.deskscreen.ticker
import
com.github.thomasnield.rxkotlinfx.doOnNextFx
import
io.reactivex.BackpressureStrategy
import
io.reactivex.Flowable
import
io.reactivex.Completable
import
io.reactivex.Observable
import
io.reactivex.
processors.PublishProcessor
import
io.reactivex.
disposables.Disposable
import
io.reactivex.schedulers.Schedulers
import
javafx.animation.Animation
import
javafx.animation.KeyFrame
...
...
@@ -22,17 +21,17 @@ import java.time.LocalDateTime
import
java.time.ZonedDateTime
import
java.time.format.DateTimeFormatter
import
java.util.concurrent.ConcurrentLinkedQueue
import
java.util.concurrent.Executors
import
java.util.concurrent.TimeUnit
import
java.util.concurrent.atomic.AtomicBoolean
//Generic the shit out of this!
data class
TickerEntry
<
T
:
Node
>(
val
title
:
String
,
val
content
:
T
,
val
reschedule
:
Boolean
=
false
,
val
update
:
((
content
:
T
)
->
Unit
)?
=
null
val
update
:
((
content
:
T
)
->
Unit
)?
=
null
,
val
updateObservable
:
((
content
:
T
)
->
Completable
)?
=
null
)
//TODO: need to add a size buffer, some dynamic things get weird
/**
* Display a ticker view of things
...
...
@@ -82,23 +81,40 @@ class TickerView : View() {
//Lets create one that does a time
//This causes the lag that causes them to smear together, need to update the rendering logic to not be threaded
// per each item
val
time
=
TickerEntry
(
"CurrentTime"
,
createText
(
"one"
)
)
{
text
->
val
time
=
TickerEntry
(
"CurrentTime"
,
createText
(
"one"
)
,
update
=
{
text
->
//Automatically called by the thing on the right thread...
text
.
text
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE_TIME
)
//TODO: needs a way of cancelling
}
})
//TODO: with a subscription, I need a way to unsub it later
val
time2
=
TickerEntry
(
"CurrentTimeObserved"
,
createText
(
"one"
),
updateObservable
=
{
text
->
Observable
.
interval
(
1
,
TimeUnit
.
SECONDS
)
.
doOnNextFx
{
text
.
text
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE_TIME
)
}
.
doOnNext
{
logger
.
debug
(
"Updating time! -- $it"
)
}
.
ignoreElements
()
})
val
entries2
=
listOf
(
time2
as
TickerEntry
<
Node
>,
TickerEntry
<
Node
>(
"time"
,
createText
(
ZonedDateTime
.
now
().
format
(
DateTimeFormatter
.
ISO_ZONED_DATE_TIME
)))
)
val
entries
=
listOf
(
//TODO: I have to put way too much crap into that to make this work, something isn't right
time
as
TickerEntry
<
Node
>,
TickerEntry
<
Node
>(
"blorp"
,
createText
(
"Third!"
)),
TickerEntry
<
Node
>(
"blerp"
,
createText
(
"this is the second"
)),
time
as
TickerEntry
<
Node
>,
TickerEntry
<
Node
>(
"test"
,
createText
(
sampleText
)),
TickerEntry
<
Node
>(
"more"
,
createText
(
"Some more words"
)),
//
time as TickerEntry<Node>,
//
TickerEntry<Node>("test", createText(sampleText)),
//
TickerEntry<Node>("more", createText("Some more words")),
TickerEntry
<
Node
>(
"another"
,
createText
(
"This is happening"
)),
TickerEntry
<
Node
>(
"time"
,
createText
(
ZonedDateTime
.
now
().
format
(
DateTimeFormatter
.
ISO_ZONED_DATE_TIME
)))
)
Observable
.
fromIterable
(
entries
)
.
observeOn
(
Schedulers
.
computation
())
.
concatMap
{
tickerEntry
->
...
...
@@ -114,6 +130,13 @@ class TickerView : View() {
marqueeView
.
enqueueTickEntry
(
it
)
}
.
subscribe
()
Observable
.
fromIterable
(
entries2
)
.
delay
(
30
,
TimeUnit
.
SECONDS
)
.
doOnNext
{
logger
.
debug
(
"Adding older things"
)
marqueeView
.
enqueueTickEntry
(
it
)
}.
subscribe
()
}
}
...
...
@@ -122,7 +145,7 @@ class MarqueeView : View() {
private
val
logger
=
KotlinLogging
.
logger
{}
private
val
OFFSET
=
5.0
//Amount of space between entries!
// val tickEntries = PublishProcessor.create<TickerEntry<Node>>()
// val tickEntries = PublishProcessor.create<TickerEntry<Node>>()
private
val
activeTicks
=
ConcurrentLinkedQueue
<
TickerEntry
<
Node
>>()
//This might not need to be threadsafe, only one thing is adding/removing it
private
val
queuedTicks
=
ConcurrentLinkedQueue
<
TickerEntry
<
Node
>>()
//This one does, multiple threads!
...
...
@@ -149,49 +172,12 @@ class MarqueeView : View() {
rectangle
.
heightProperty
().
bind
(
root
.
heightProperty
())
root
.
clip
=
rectangle
// //TODO: I don't have a good way of cleaning up the executor service. Need to figure that out
//
// //Create a backpressure based flowable that only processes one item at a time.
// Flowable.create<TickerEntry<Node>>({ emitter ->
// tickEntries
// .observeOn(Schedulers.computation())
// .doOnNext {
// logger.debug("About to emit!")
// emitter.onNext(it) //NOTE: This blocks a thread
// logger.debug("Emitted incoming ticker change!")
// }
// .subscribe()
//
// //There isn't really a cancellation thing...
// },
// BackpressureStrategy.BUFFER) //Auto buffer of 128
// .observeOn(Schedulers.from(singleThread))
// //.onBackpressureBuffer(1024)
// .doOnNext {
// logger.debug("Backpressure driven work for $it")
// }
// .map {
// //start an animation, but don't release another until it's past a point
// //Except cannot.... or consume a blocking, but that's gross, probably easier tho....
// //do some kind of callback thing like for steam
// val clearedSubject = PublishProcessor.create<ClearedTick>()
// Pair(it, clearedSubject)
// }
// .doOnNextFx {
// //This will fire off the request to animate the entry, and not move forward until the next thing passes. I think
// animateText(it.first, it.second)
// }
// .map {
// //NOTE: thread gets blocked here, not sure if it matters.
// it.second.blockingFirst()
// }
// .subscribe()
startAnimation
()
//Fire up the animation process
//TODO: need to have a way of monitoring the queue and active to pause/start the whole thing again
//Maybe that doesn't matter? Will have to monitor CPU usage
}
fun
enqueueTickEntry
(
entry
:
TickerEntry
<
Node
>)
{
fun
enqueueTickEntry
(
entry
:
TickerEntry
<
Node
>)
{
queuedTicks
.
add
(
entry
)
}
...
...
@@ -203,19 +189,21 @@ class MarqueeView : View() {
val
timeline
=
Timeline
()
//TODO: need to find a way to pause/restart the timeline when it's empty
fun
lastOneCleared
()
:
Boolean
{
fun
lastOneCleared
():
Boolean
{
//Determine if the last entry in the activeQueue has cleared
val
last
=
activeTicks
.
last
()
return
last
.
content
.
layoutBounds
.
width
+
last
.
content
.
layoutX
+
OFFSET
<=
root
.
width
}
val
subscriptions
=
HashMap
<
TickerEntry
<
Node
>,
Disposable
>()
//I want any rendering delays to slow the whole thing, not just the individual item. Also then it's less threads
val
updateFrame
=
KeyFrame
(
Duration
.
millis
(
35.0
),
EventHandler
<
ActionEvent
>
{
//This appears to move too fast!
if
(
activeTicks
.
isEmpty
()
||
lastOneCleared
())
{
if
(
activeTicks
.
isEmpty
()
||
lastOneCleared
())
{
val
newTickerEntry
:
TickerEntry
<
Node
>?
=
queuedTicks
.
poll
()
if
(
newTickerEntry
!=
null
)
{
if
(
newTickerEntry
!=
null
)
{
//Can take one from the queued thing, and plop it in here to start rendering
//TODO: all the pre-setup here
//Then just put it into the active queue, so it will start processing like normal
...
...
@@ -242,13 +230,26 @@ class MarqueeView : View() {
//Now I ned to figure out how to remove it
entry
.
content
.
removeFromParent
()
//Is this legit?
activeTicks
.
remove
(
entry
)
//no longer here, shouldn't ruin the loop
if
(
subscriptions
.
containsKey
(
entry
))
{
subscriptions
[
entry
]
!!
.
dispose
()
//This should cancel it
logger
.
debug
(
"disposing observable for ${entry.title}"
)
}
}
else
{
content
.
layoutX
=
content
.
layoutX
-
1
//TODO: I should probably do animation in larger chunks perhaps, so that it can use less CPU?
//TODO: this might be where I need to make other animation happen
if
(
entry
.
update
!=
null
)
{
timeline
.
pause
()
//TODO: I think I cannot update an entry while it's being animated? I'm not sure
//At the least, i don't have to pause it, lets see if I can thread that
//timeline.pause()
entry
.
update
.
invoke
(
entry
.
content
)
timeline
.
play
()
//timeline.play()
}
if
(
entry
.
updateObservable
!=
null
&&
!
subscriptions
.
containsKey
(
entry
))
{
//Start up the observable that updates the UI
logger
.
debug
(
"Starting observable for ${entry.title}"
)
val
disposable
=
entry
.
updateObservable
.
invoke
(
entry
.
content
).
subscribe
()
subscriptions
.
put
(
entry
,
disposable
)
//Need a thing to track these with
}
}
}
...
...
@@ -265,61 +266,4 @@ class MarqueeView : View() {
logger
.
debug
(
"TIMELINE FINISHED HANDLER!"
)
}
}
//Will need to fire one of these for each thing, with some space detection, based on when one is ending.
private
fun
animateText
(
entry
:
TickerEntry
<
Node
>,
publishSubject
:
PublishProcessor
<
ClearedTick
>)
{
val
content
=
entry
.
content
val
emptyStart
=
root
.
prefWidth
//It's weird, like a race condition
logger
.
debug
(
"NEW LAYOUT START X: ${emptyStart}"
)
content
.
layoutX
=
emptyStart
//I think this is right
root
.
add
(
content
)
//stick it in there, and start animating it
val
cleared
=
AtomicBoolean
(
false
)
val
timeline
=
Timeline
()
//TODO: need to establish a single timeline, not a timeline per element
//I want any rendering delays to slow the whole thing, not just the individual item. Also then it's less threads
val
updateFrame
=
KeyFrame
(
Duration
.
millis
(
35.0
),
EventHandler
<
ActionEvent
>
{
//I don't intend to bounce this
val
textWidth
=
content
.
layoutBounds
.
width
val
paneWidth
=
root
.
width
val
layoutX
=
content
.
layoutX
//instead I want to move completely from the right all the way off until it's gone
//end state of this text is the entire width of the text off the right so layoutX <= 0 - textWidth - offset
if
(
textWidth
+
layoutX
+
OFFSET
<=
paneWidth
&&
!
cleared
.
get
())
{
//There's enough room to start another, how can I notify this?
publishSubject
.
onNext
(
ClearedTick
(
entry
))
cleared
.
set
(
true
)
publishSubject
.
onComplete
()
//taht should make it repeat
}
if
(
layoutX
<=
0
-
textWidth
-
(
2
*
OFFSET
))
{
logger
.
debug
(
"ANIMATED COMPLETELY OUT THE PANEL!"
)
//Done animating, it's finished
timeline
.
stop
()
}
else
{
content
.
layoutX
=
content
.
layoutX
-
1
//TODO: I should probably do animation in larger chunks perhaps, so that it can use less CPU?
//TODO: this might be where I need to make other animation happen
if
(
entry
.
update
!=
null
)
{
timeline
.
pause
()
entry
.
update
.
invoke
(
entry
.
content
)
timeline
.
play
()
}
}
})
timeline
.
keyFrames
.
add
(
updateFrame
)
//Will have to figure out how to inject changes in here
timeline
.
cycleCount
=
Animation
.
INDEFINITE
//Because we're going to manually tell it to stop
timeline
.
play
()
timeline
.
onFinished
=
EventHandler
<
ActionEvent
>
{
//This never gets triggered, even when timeline.stop() is called.
logger
.
debug
(
"TIMELINE FINISHED HANDLER!"
)
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment