001package polaris.sdk.ui.samples;
002
003import polaris.sdk.ui.*;
004import polaris.sdk.ui.controls.*;
005import java.util.*;
006import static polaris.sdk.ui.samples.SceneSupport.*;
007
008/** A playback-control example with a deterministic local clock; no audio/network is loaded. */
009public final class MusicScene extends SceneSupport.AppScene {
010 public static final String[] TRACKS={"Blue Hour","Soft Current","Afterglow","Quiet Room","Night Walk"};
011 private static final int[] DURATIONS={204,187,236,168,215};
012 private final Panel album=new Panel(),playlist=new Panel(),transport=new Panel();private final Artwork cover=new Artwork("record");
013 private final Label name=title(TRACKS[0]),artist=caption("Glass Sessions  /  本地示例曲目"),state=caption("已暂停"),time=caption("00:00 / 03:24");
014 private final Badge type=new Badge("AMBIENT");private final ListBox tracks=new ListBox(TRACKS);private final SearchField search=new SearchField();private final List<Integer> filtered=new ArrayList<Integer>();
015 private final ToggleButton like=new ToggleButton("收藏");private final Button previous=new Button("上一首"),play=new Button("播放").primary(true),next=new Button("下一首");
016 private final Slider progress=new Slider(),volume=new Slider();private final Switch loop=new Switch("单曲循环"),shuffle=new Switch("随机播放");private final Label volumeLabel=caption("音量 65%");
017 private final Label disclaimer=caption("仅演示播放状态、列表和进度联动;不含音频文件,不会发声。");
018 private final boolean[] favorites=new boolean[TRACKS.length];private final Random random=new Random(42);
019 private int index;private boolean playing;private double elapsed,lastTick=-1;
020 public MusicScene(){super("Glass Music","播放器 / 播放暂停、队列、拖动进度、收藏与播放模式");id("scene.music");
021  mount(this,album,"music.album");mount(this,playlist,"music.playlist");mount(this,transport,"music.transport");mount(this,disclaimer,null);
022  mount(album,cover,null);mount(album,name,"music.track");mount(album,artist,null);mount(album,type,null);mount(album,like,"music.like");like.onChange(v->favorites[index]=like.selected());mount(album,state,"music.state");
023  mount(playlist,search,"music.search");search.placeholder("搜索示例曲目…");search.onChange(v->filter());mount(playlist,tracks,"music.tracks");tracks.glass(false).padding(0).rowHeight(49).rowGap(8);tracks.onSelection(i->{if(i>=0&&i<filtered.size())selectTrack(filtered.get(i));});
024  mount(playlist,loop,"music.loop");mount(playlist,shuffle,"music.shuffle");mount(playlist,volumeLabel,"music.volumeLabel");mount(playlist,volume,"music.volume");volume.range(0,100).value(65);volume.onChange(v->volumeLabel.text("音量 "+Math.round(volume.value())+"% · 模拟"));
025  mount(transport,previous,"music.previous");previous.onClick(v->change(-1));mount(transport,play,"music.play");play.onClick(v->{playing=!playing;lastTick=clock();refresh();});mount(transport,next,"music.next");next.onClick(v->change(1));mount(transport,progress,"music.progress");progress.range(0,DURATIONS[0]).continuous();progress.onChange(v->{elapsed=progress.value();refresh();});mount(transport,time,"music.time");filter();refresh();
026 }
027 public boolean playing(){return playing;}
028 public int trackIndex(){return index;}
029 public double position(){return elapsed;}
030 public boolean liked(int index){return favorites[index];}
031 public void selectTrack(int selected){if(selected<0||selected>=TRACKS.length)throw new IllegalArgumentException("track");if(index!=selected){index=selected;elapsed=0;}progress.range(0,DURATIONS[index]);refresh();}
032 private void filter(){filtered.clear();List<String> names=new ArrayList<String>();String q=search.value().trim().toLowerCase(Locale.ROOT);for(int i=0;i<TRACKS.length;i++)if(TRACKS[i].toLowerCase(Locale.ROOT).contains(q)){filtered.add(i);names.add(String.format(Locale.ROOT,"%02d   %s    %d:%02d",i+1,TRACKS[i],DURATIONS[i]/60,DURATIONS[i]%60));}tracks.items(names);if(filtered.contains(index))tracks.select(filtered.indexOf(index));}
033 private void change(int direction){int selected=shuffle.selected()?(index+1+random.nextInt(TRACKS.length-1))%TRACKS.length:(index+direction+TRACKS.length)%TRACKS.length;selectTrack(selected);if(filtered.contains(index))tracks.select(filtered.indexOf(index));}
034 private void refresh(){name.text(TRACKS[index]);play.text(playing?"暂停":"播放");state.text(playing?"播放进度模拟中":"已暂停 · 无音频输出");like.selected(favorites[index]);time.text(clockText(elapsed)+" / "+clockText(DURATIONS[index]));progress.value(elapsed);}
035 private static String clockText(double seconds){int s=(int)seconds;return String.format(Locale.ROOT,"%02d:%02d",s/60,s%60);}
036 @Override public void attach(Ui context){if(context==null)playing=false;lastTick=-1;super.attach(context);}
037 @Override public float preferredHeight(float width){return width<760?986:598;}
038 @Override protected void layoutChildren(){header();double now=clock();if(lastTick>=0&&playing){elapsed+=Math.min(.20,Math.max(0,now-lastTick));if(elapsed>=DURATIONS[index]){if(loop.selected())elapsed=0;else change(1);}refresh();}lastTick=now;
039  boolean stacked=w<760;float aw=stacked?w:Math.min(340,w*.37f),pw=stacked?w:w-aw-GAP;
040  place(album,0,76,aw,376);place(playlist,stacked?0:aw+GAP,stacked?464:76,pw,376);
041  place(cover,16,16,aw-32,224);place(name,16,256,aw-32,27);place(artist,16,292,aw-32,21);place(type,16,331,95,25);place(like,aw-110,326,94,32);place(state,16,album.height()-2,aw-32,0);state.visible(false);
042  place(search,16,16,pw-32,34);place(tracks,16,62,pw-32,230);place(loop,16,309,(pw-44)/2,30);place(shuffle,28+(pw-44)/2,309,(pw-44)/2,30);
043  place(volumeLabel,16,350,152,22);place(volume,174,346,pw-190,27);
044  float y=Math.max(album.y()+album.height(),playlist.y()+playlist.height())+12;place(transport,0,y,w,92);
045  place(previous,16,16,88,32);place(play,116,16,100,32);place(next,228,16,88,32);place(time,w-152,23,136,22);place(progress,16,58,w-32,22);
046  place(disclaimer,0,y+108,w,22);h=y+134;
047 }
048}