JavaFX media volume bindings - javafx

I am working on small game in JavaFX.
I have an utility class to manage music:
private static Map<SongEnum, Media> songs = new EnumMap<>(SongEnum.class);
private static MediaPlayer currentSong;
public static void playSong(SongEnum song) {
if(songs == null && currentSong != null) {
currentSong.stop();
currentSong = null;
}
Media media = songs.get(song);
if(media == null) {
String path = ApplicationUtils.class.getResource(song.getPath()).toExternalForm();
media = new Media(path);
songs.put(song, media);
}
if(currentSong != null) {
if(currentSong.getMedia() == media)
return;
currentSong.stop();
}
currentSong = new MediaPlayer(media);
currentSong.setCycleCount(MediaPlayer.INDEFINITE);
currentSong.play();
}
I want to be able to play many different songs - is above approach correct?
Also I am not sure how to implement volume-management system in this case. My first thought was to bind some slider property (which lies in another class) with MediaPlayer property, but in this case it will change everytime the songs changes. So, what is the best way to achieve it?

In the game we're currently working on, we just used the volume property of the MediaPlayer. We put 0.3 on the background theme, and 0.8or 1on effects as they should be higher than the background theme. Test it out see how it works best by using currentSong.setVolume("0 to 1");. As of using a slider, why don't just use a setOnEndOfMediato loop the song. With this, the volume shouldn't change. This only works if you of course are only looping the same song.
currentSong.setOnEndOfMedia(() -> {
currentSong.seek(Duration.ZERO);
currentSong.play();
});
If not, I would add make currentSong static, and then access it like "YourMediaClass.currentSong.setVolume("slider.getSliderValue or whatever you use"). This is probably what you're looking for.

Related

In Blazor, is there a better way to add elements from a list?

I'm doing some tests with DotNet 5rc1 and Blazor, and have noticed something that makes me think I'm not doing this right.
I have a demo page (code below) that gives the user a set of buttons and they can click on a button to do a die roll. In the client side C# I then roll the die and prepend the roll string to a list. In the template, I then do a foreach and render the user's past rolls.
The issue is that I've been watching the Websocket messages and each time I add a new element, the message being passed gets larger and larger. This is because the entire list is being re-rendered with each click.
Is there a better way to do this, so that Blazor only inserts the new item in the DOM and sends down a trivial message, rather than re-rendering the entire section of the page? For this example, it doesn't matter much, but if I were building a bigger app with a lot of interactions, I could see this becoming a bottleneck.
Here's the code for my page - this is just a new page in a brand new Blazor project. (My code has more than a 1d20 button, but I removed it from here for brevity.)
#page "/dieroller"
<h1>Die Roller Utility</h1>
...
<button class="btn btn-primary" #onclick="#(e => RollDie(20))">
<i class="fas fa-dice-d20"></i>
</button>
<h2 class='mt-5'>Your Roll History</h2>
#foreach (string roll in rolls) {
<p>#roll</p>
}
#code {
#using System.Threading;
private List<string> rolls = new List<string>();
private void RollDie(int upper)
{
Random rand = new Random();
rolls.Insert(0, string.Format("1d{0} = {1}", upper, rand.Next(1, upper)));
}
}
The direct answer to your problem is to use #key. That is always a good idea when a loop is involved.
The problem is that you need unique keys and your strings don't fit that requirement.
A blunt solution is to introduce a class (not a struct), just to get unique keys. I adapted your code to the following:
#foreach (Roll roll in rolls)
{
<p #key="roll">#($"d={roll.Upper} {roll.Value}")</p>
}
#code {
class Roll
{
public int Upper { get; set; }
public int Value { get; set; }
}
private List<Roll> rolls = new List<Roll>();
private void RollDie(int upper)
{
Random rand = new Random();
rolls.Insert(0, new Roll { Upper = upper, Value = rand.Next(1, upper + 1) });
}
}
Using this you do get stable, non-growing WS packets.
Based on #BrianParker's comment above - Yes, you need a key so that Blazor knows which items need to be updated. That Key also needs to be unique, or you'll generate errors with key collision.
Because these are strings, they don't make good keys - therefore I ended up retooling this with a DieRoll class that I could put in a list. Here's the new code behind:
using System;
using System.Collections.Generic;
namespace BlazorApp.Pages
{
public partial class DieRoller
{
private List<DieRoll> rolls = new List<DieRoll>();
private void RollDie(int upper)
{
rolls.Insert(0, new DieRoll(upper));
}
}
public class DieRoll
{
public int Roll;
public int DieType;
public DieRoll(int DieType) {
this.DieType = DieType;
RollDie();
}
public int RollDie() {
Random rand = new Random();
this.Roll = rand.Next(1, DieType + 1);
return Roll;
}
public override string ToString()
{
return string.Format("1d{0} = {1}", this.DieType, this.Roll);
}
}
}
And here's the new template code at the p tag:
#foreach (DieRoll die in rolls) {
<p #key="die">#die</p>
}
Obviously this is more complex, but this is what works. The messages from the server are much smaller, and never grow in size.
Also, this might NOT have mattered if I wasn't prepending the items to the List. Appending to the list might have let Blazor understand where the elements were being created easier. But I didn't bother to test that theory.
You can see another example here: https://blazor-university.com/components/render-trees/optimising-using-key/
With List.Add the messages size remains the same, so why is List.Insert any different? Presumably it's due to the inherent performance penalty that List.Insert(index: 0) incurs since the underlying array needs to be reshuffled, see here: https://stackoverflow.com/a/18587349/4000335
To verify this even using List.Insert(List.Count) shows the message size remains the same (as is the case with Add).
If the requirement is to have the last roll on top then an optimal approach would probably involve JavaScript, which defeats the purpose of using blazor server in the first place, however, blazor webassembly doesn't need to communicate with a server to accomplish this since it's all done on the client.

TornadoFX/JavaFX treeview doesnt repopulate correctly after mutating multiple ObservableList

Problem description
I have a treeview (TornadoFX) which gets populated with 3 Observable Lists. If I mutate the 3 lists (first clear them than add new content) the treeview only contains the content of one of the Observable Lists.
The interpretation of my debugging
The treewiev actualises itself in the moment the first List haves its new content and the other 2 are still empty. It does not actualise itself as soon as the other 2 Lists gain their new content.
What I have tried so far
calling treeview.root.children.clear() before I mutate the List to empty the treewiev (same outcome)
calling treeview.refresh() after all Lists have been set (same outcome)
My (more or less pseudo) Code
{//Classes
R extends X
A(val name :String) extends X
B(val name :String, val A :String) extends X
C(val B :String) extends X
}
{//this is how the Lists get their content each time
private val aList = observableListOf<A>()
private val bList = observableListOf<B>()
private val cList = observableListOf<C>()
fun setLists(){
a.clear()
b.clear()
c.clear()
//...
a.add(...)
b.add(...)
c.add(...)
}
}
{
treeview<X> {
root = TreeItem(R)
populate{ parent ->
if (parent == root){
aList
} else if (parent.value is A){
bList.filter { it.A == parent.value.name } //works always
}else if (parent.value is B){
cList.filter { it.B == parent.value.name } //works at first start but not after set setLists() get recalled
}else {
emptyList<X>() //not sure if this is really needed but the compiler needs something here
}
}
}
}
Side Question
Ignore this Question if you want: Intelij is able to run my TornadoFX application (from main or from App with the TornadoFX Plugin), but if I try to export it it doesnt run (neither the jar nor the jnlp). Even if I select TornadoFX and all the other Libraries in the Artifact menu. BUT: if I select Native Bundle -> image in the JavaFX tab, the resulting exe (and only the exe) is working. I also tried to create a shadowJar with com.github.johnrengelman.shadow 5.2.0 Plugin but that also didnt work. So how can I export the Program as a jar (a fatjar including TornadoFX in best case)?
Well I don't know if it's the perfect solution but I simply clear the children and populate the treeview again. It works, but if that is the intended way it's not as "reactive" as I thought it is.

Multiple TrackingParticipants not working, have funny side effects?

We are rying to use WF with multiple tracking participants which essentially listen to different queries - one for activity states, one for custom tracknig records which are a subclass of CustomTrackingRecord.
The problem is that we can use both TrackingParticipants indivisually, but not together - we never get our subclass from CustomTrackingRecord but A CustomTrackingRecord.
If I put bopth queries into one TrackingParticipant and then handle everythign in one, both work perfectly (which indicates teh error is not where we throw them).
The code in question for the combined one is:
public WorkflowServiceTrackingParticipant ()
{
this.TrackingProfile = new TrackingProfile()
{
ActivityDefinitionId = "*",
ImplementationVisibility = ImplementationVisibility.All,
Name = "WorkflowServiceTrackingProfile",
Queries = {
new CustomTrackingQuery() { Name = "*", ActivityName = "*" },
new ActivityStateQuery() {
States = {
ActivityStates.Canceled,
ActivityStates.Closed,
ActivityStates.Executing,
ActivityStates.Faulted
}
},
}
};
}
When using two TrackingParticipants we have two TrackingProfile (with different names) that each have one of the queries.
in the track method, when using both separate, the lines:
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
Console.WriteLine("*** ActivityTracking: " + record.GetType());
if (record is ActivityBasedTrackingRecord)
{
System.Diagnostics.Debugger.Break();
}
never result in the debugger hitting, when using only the one to track our CustomTrackingRecord subclass (ActivityBasedTrackingRecord) then it works.
Anyone else knows about this? For now we have combined both TrackingParticipants into one, but this has the bad side effect that we can not dynamically expand the logging possibilities, which we would love to. Is this a known issue with WWF somewhere?
Version used: 4.0 Sp1 Feature Update 1.
I guess I encounterad the exact same problem.
This problem occurs due to the restrictions of the extension mechanism. There can be only one instance per extension type per workflow instance (according to Microsoft's documentation). Interesting enough though, one can add multiple instances of the same type to one workflow's extensions which - in case of TrackingParticipant derivates - causes weird behavior, because only one of their tracking profiles is used for all participants of the respective type, but all their overrides of the Track method are getting invoked.
There is a (imho) ugly workaround to this: derive a new participant class from TrackingParticipant for each task (task1, task2, logging ...)
Regards,
Jacob
I think that this problem isn't caused by extension mechanism, since DerivedParticipant 1 and DerivedParticipant 2 are not the same type(WF internals just use polymorphism on the base class).
I was running on the same issue, my Derived1 was tracking records that weren't described in its profile.
Derived1.TrackingProfile.Name was "Foo" and Derived2.TrackingProfile.Name was null
I changed the name from null to "Bar" and it worked as expected.
Here is a WF internal reference code, describing how is the Profile selected
// System.Activities.Tracking.RuntimeTrackingProfile.RuntimeTrackingProfileCache
public RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
{
RuntimeTrackingProfile runtimeTrackingProfile = null;
HybridCollection<RuntimeTrackingProfile> hybridCollection = null;
lock (this.cache)
{
if (!this.cache.TryGetValue(rootElement, out hybridCollection))
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection = new HybridCollection<RuntimeTrackingProfile>();
hybridCollection.Add(runtimeTrackingProfile);
this.cache.Add(rootElement, hybridCollection);
}
else
{
ReadOnlyCollection<RuntimeTrackingProfile> readOnlyCollection = hybridCollection.AsReadOnly();
foreach (RuntimeTrackingProfile current in readOnlyCollection)
{
if (string.CompareOrdinal(profile.Name, current.associatedProfile.Name) == 0 && string.CompareOrdinal(profile.ActivityDefinitionId, current.associatedProfile.ActivityDefinitionId) == 0)
{
runtimeTrackingProfile = current;
break;
}
}
if (runtimeTrackingProfile == null)
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection.Add(runtimeTrackingProfile);
}
}
}
return runtimeTrackingProfile;
}

How to use Tween for MovieClips in a Flex 4.5 application?

I'm new to Flex and am trying to port a pure Flash/AS3 card game to Flex 4.5.
It works mostly well, but I'm missing few puzzle parts there:
I've created a custom component based on UIComponent representing a deck of cards (which are an array of Sprites or MovieClips):
In the original pure Flash/AS3 game I was using Tween for the 3 cards at the table - to show the game user, who has put which card (by sliding them towards playing table middle):
import fl.transitions.*;
import fl.transitions.easing.*;
public class Deck extends UIComponent {
private var _card:Array = new Array(3);
private var _tween:Array = new Array(3);
....
override protected function createChildren():void {
_tween[YOU] = new Tween(_card[0], 'y', Regular.easeOut, _card[0].y + 40, _card[0].y, .5, true);
_tween[LEFT] = new Tween(_card[1], 'x', Regular.easeOut, _card[1].x - 40, _card[1].x, .5, true);
_tween[RIGHT] = new Tween(_card[2], 'x', Regular.easeOut, _card[2].x + 40, _card[2].x, .5, true);
....
However Flash Builder 4.5 doesn't seem to know fl.transitions.* packages at all?
Does anybody please have an advice on how to use Tween here?
Like I've written the rest (my custom Flex component, moving card-Sprites around, etc.) works well. Only the Tween lines had to be commented.
Thank you!
Alex
Take a look at the Tweener class on Google Code. With it you can specify and object, and a map of its properties and their desired values, along with time, and it will 'tween' those properties from their current to their desired values.
Tweener.addTween(yourCard, {y:50, time:1});//for a 1 second tween
My first knee-jerk reaction has been to add flash.swc (delivered by Flash CS Pro) to the Flex build path and then:
import fl.transitions.*;
import fl.transitions.easing.*;
can be used again.
But in the long-term I will probably write my own function to move the playing cards and run it on Event.ENTER_FRAME. Because I don't want to include Tweener or Tweenlite libraries for mere card sliding.
_tween[0] = _card[0].y;
_tween[1] = _card[1].x;
_tween[2] = _card[2].x;
....
_card[0].y = _tween[0] + 20;
addEventListener(Event.ENTER_FRAME, slideCardYou);
_card[1].x = _tween[1] - 20;
addEventListener(Event.ENTER_FRAME, slideCardLeft);
_card[2].x = _tween[2] + 20;
addEventListener(Event.ENTER_FRAME, slideCardRight);
....
private function slideCardYou(event:Event):void {
if (_card[0].y-- < _tween[0])
removeEventListener(Event.ENTER_FRAME, slideCardYou);
}
private function slideCardLeft(event:Event):void {
if (_card[1].x++ > _tween[1])
removeEventListener(Event.ENTER_FRAME, slideCardLeft);
}
private function slideCardRight(event:Event):void {
if (_card[2].x-- < _tween[2])
removeEventListener(Event.ENTER_FRAME, slideCardRight);
}
Also I've looked at mx.effects.Tween and spark.effects.Animate but they seem to be more appropriate for UIComponents and not Sprites as in my case.

Flex components with dynamic data update

I need to push real time data to a Flex GUI (Data grid), but each time new data is pushed to the grid, it's losing its previous state.
Example:
if I scrolled to the right, after the next update scrolls come back to the default position, that is, left
if I am selecting any row, it's getting unselected just after update.
Is there a way to maintain the state?
I am using Flex 3. I can move to Flex 4 if it helps.
How do you set the data? Do you change the DataGrid dataProvider with a new collection of objects ? Because, from the behavior described by you this may be the case.
The solution would be instead changing the dataProvider of the DG. Each time, you should just update the values in the collection which is assigned as data provider.
For example,
[Bindable]
var myDataCollection:ArrayCollection = new ArrayCollection([0,1,2,3,4]);
// Handle creation complete.
private function onCreationComplete():void
{
initDG();
}
// Init DG data provider just once.
private function initDG(data:ArraCollection):void
{
myDG.dataProvider = data;
}
private function updateDG_Method_1(row:int, value:int):void
{
var data:ArrayCollection = myDG.dataProvider as ArrayCollection;
if(data && data.length > row)
{
data[row] = value;
}
// We can force refresh if not not done automatically.
myDG.invalidateList();
myDG.validateNow();
}
private function updateDG_Method_2(row:int, value:int):void
{
if(myDataCollection && myDataCollection.length > row)
{
myDataCollection[row] = value;
}
// We can force refresh if not not done automatically.
myDG.invalidateList();
myDG.validateNow();
}
Please ignore/correct any typo .. since I did not test this :))
Good luck!

Resources