Grid dokáže být až překvapivě tvárný a dají se v něm dělat i složitější věci, doslova od píky.
Co udělat horizontální slider, jako třeba tento?
Začátek třídy
Začneme tím, že si vytvoříme Widget a pojmenujeme si ho Hslider.
Vyjměčně nezačneme s context.addSizeProfiles ale s třídou Slider.
classSlider{}
a přidáme do naší třídy několik promněných pro práci s ní:
classSlider {protected _emitter:WK.Emitter<WK.Event>;// slouží k tomu, abychom mohli přidat event.listener na tento objektprotected _rootView:WK.View; // main element, aby to všechno bylo na co dát (něco jako context.root) pro tuto tříduprotected _backgroundElement:WK.View; // na celkové podbarvení paneluprotected _valueElement:WK.Element; // na podbarvení vybrání pod tlačítkemprotected _buttonElement:WK.Button; // na vytvoření kulatého tlačítkaprotected _reversed:boolean=false; // později budeme chtít možnost "invertovat graf" protected _moving =false; protected _movingOffset =0; //je můžné, že tlačítko bude např. v půli grafu, zde si budeme ukládat aktuální posun tlačítka od 0 protected _value =0; constructor(){ } }
Konstruktor třídy:
a v kontsruktoru je vytvoříme a nastylujeme.
Samozřejmě pro vytvoření prvků potřebujeme context, proto si ho prřidáme do konstruktoru.
constructor(context: WidgetContext){this._emitter =newWK.Emitter<WK.Event>(); //inicializaace emitoruthis._rootView =newWK.View(context); //inicializace našeho root.view//přidáme stylizacithis._rootView.style.padding ="22px"; // chceme, aby posuvník byl uprostřed 1 čtverečkuthis._rootView.style.background ="transparent"; // necheme pozadí, proto průhledná this._backgroundElement =newWK.View(context); // inicializacethis._backgroundElement.style.background ="#838383"; // nastaavíme šedou po celé délcethis._backgroundElement.style.height ="10px"; // tímto z toho uděláme tenký proužekthis._backgroundElement.style.y ="-3px"; // kvůli paddingu děláme drobnou korekturuthis._backgroundElement.style.width ="100%"; // roztáhneme ho po celé šíři widgetuthis._backgroundElement.style.radius ="5px"; //zakulatíme rohythis._rootView.add(this._backgroundElement); //přidáme element na náš základní elementthis._valueElement =newWK.Element(context); // inicializacethis._valueElement.style.background ="#2274ff";this._valueElement.style.height ="10px";this._valueElement.style.y ="0";this._valueElement.style.width ="0";this._valueElement.style.radius ="0";this._backgroundElement.add(this._valueElement);this._buttonElement =newWK.Button(context,"");// inicializacethis._buttonElement.style.width ="30px"; //this._buttonElement.style.height ="30px"; // nastavíme velikosst tlačítka napevnothis._buttonElement.style.x ="0"; this._buttonElement.style.y ="2px";this._buttonElement.style.radius ="15px"//tímto tlačítko bude kulatéthis._buttonElement.style.border ="#FFFFFF 5px"; //nastavíme hezký, velký zdobný okrajthis._buttonElement.style.background ="#2274ff";this._buttonElement.style.shadow ="#000000 30px 0 0"; //vytvoříme stín, přímo pod tlačítkemthis._buttonElement.style.originX ="0.5";this._buttonElement.style.originY ="0.5"; this._rootView.add(this._buttonElement);}
A nakonec přidáme několik listenerů do _constructor_u, abychom mohli manipulovat s posuvníkem
this._buttonElement.listenEvent("mousedown",this.onButtonMouseDown); //reagování při kliknutí na buttonthis._backgroundElement.listenEvent("mousedown",this.onBackgroundMouseDown); // reakce při kliknutí kamkoliv na posuvníkcontext.root.listenEvent("appmouseup",this.onAppMouseUp); //ve chvíli kdy uživatel pustí tlačítkocontext.root.listenEvent("appmousemove",this.onAppMouseMove); //kvůli celemů posuvníku je třeba vyřešit co jak moc uživatel posunul
Mock funkce pro listenery:
Jistě jste si všimli, že místo kasického e => { foo.bar() }); voláme funkci, proto si rovno vytvoříme prázdé funkce, později se k ním vrátíme.
pokud si chceme otestovat, že náš widget je nastylovaný správně, přidáme getter do naší třídy
public get rootElement(): WK.View {returnthis._rootView; }
a poté úplně mimo třídu napíšeme
context.addSizeProfiles(3,1,50,1);let slider =newSlider(context);slider.rootElement.style.width ="100%";slider.rootElement.style.height ="100%";context.root.add(slider.rootElement); //přidáme slider.rootElement, na který jsme napojili všechny ty prvky dohromady
při testování dostaneme něco takového:
Můžeme trochu upravit pozice všech objektů.
přidání getterů a pokrytí základní funkčnosti
Upravíme naší třídu. Přidáme do ní ještě několik getterů
public getButtonElement(): WK.Button {returnthis._buttonElement; } public getBackgroundElement(): WK.View {returnthis._backgroundElement; } public getValueBarElement(): WK.Element {returnthis._valueElement; } public get isMoving(): boolean {returnthis._moving; }
Abychom později mohli měnit detaily jako barvy, zaoblení rohu apod.
Jedné, co děláme je, že vrátíme přesněji typovaný emmiter, ke kterému jsme přidali specifikovaný key a callback.
Napíšeme si kód, jenž bude posouvat posuvník, pro širší využití budeme pracovat v procentech (1 = 100%, 0.5 = 50%, 0.25 = 25%)
protected internalSetValue(value: number) { this._value =Math.min(Math.max(value,0),1); //podobně jako u grafu, jak jsme vybírali větší číslothis._valueElement.style.width = value *100+"%"; //šířka podbaveníif (this._reversed) { //později přidáme i možnost mít posuvník z druhé strany, připravíme si tedy danou možnost předemthis._buttonElement.style.x = (1- value) *100+"%";// v případě že chceme invertovanou hodnotu } else {this._buttonElement.style.x = value *100+"%"; //posun tlačítka(posuvníku) na správnou pozici } }
Rovnou přidáme možnost umožnit mít posuvník "obráceně"
public setReversed(reversed: boolean) {if (this._reversed != reversed) { //pokud se hodnoty, co aktuálně máme liší od té, co chcemethis._reversed = reversed;if (this._reversed) {this._valueElement.style.originX ="1"; this._valueElement.style.x ="100%"; //tímto "překlopíme" value.element, jenž podbarvuje posuvník } else {this._valueElement.style.originX ="0";this._valueElement.style.x ="0"; }this.internalSetValue(this._value); //aby se změny dostaly i k posuvníku } }
Do budoucna ještě pro posun tlačítka
protected setButtonPosition(e: WK.MouseEvent) {constmax=this._rootView.visibleRect.size.width; //zapamatujeme si max. šířku panelulet newX =Math.min( Math.max(e.mousePosition.x -this._movingOffset,0), max); //tooto rozepíšu o kousek nížethis._value = newX / max;if (this._reversed) { //kontrolujeme zda nescrollujeme z druhé stranythis._value =1-this._value; }this.internalSetValue(this._value); //nastavíme novou "vnitřní" hodnotuthis._emitter.emit(this,newWK.Event("valueChanged")); //vyšleme změny komukoliv,kdo je poslouchá }
zaměříme se na let newX = Math.min( Math.max(e.mousePosition.x - this._movingOffset, 0), max);
nejedná se o nic složitého, jenom menší číslo, abychom nevyjeli z pole, ze dvou možných, kde ještě upravujeme, abychom se nedostali pod nulu. Nejdříve vyhodnotíme zda je pozice posuvíku (mínus offset pro správnou pozici) větší než nula a později, jestli nepřekračujeme nejvyšší možnou.
přidání funkcí
Když už jsme si konečně dopsali veškeré potřebné drobnosti pro barvení a posuv tlačítek, přidáme interaktivitu.
Přepíšeme původní prázdné funkce na:
protected onButtonMouseDown= (e) => {if (this._buttonElement.isHover) { //kontrolujeme, zda opravdu je kurzor nad tlačítkemthis._moving =true; //potvrdíme že se hýbe s posuvníkemthis._movingOffset =e.mousePosition.x -this._buttonElement.visibleRect.position.x; // uložíme si bokem offset, odkud se tlačítko posouvalo } }
Zde nám pouze stačí "přepnout" boolean na přesun, změnu hodnoty provedeme v "onAppMouseMove".
samozřejmě, kromada uživatelů spíše než tahem tlačítka preferuje klikání přímo na lištu posuvníku, proto přepíšeme interaktivitu k němu
protected onBackgroundMouseDown= (e) => {if (this._backgroundElement.isHover &&!this._buttonElement.isHover) { //kontrolujeme, zda je myš na liště, ale né na tlačítku (pustili bychom tímto dvě funkce najednou)this._movingOffset =this._buttonElement.visibleRect.size.width /2;this.setButtonPosition(e); } }
Všimněte si, že zde _moving neupravujeme. přednostně kvůli tomu, že ve chvíli co se klikne na danou pozici
Při puštění vypneme "moving" abychom neposouvali tačítko když nechceme
slider.listenEvent("valueChanged",function(e) { //posloucháme a pošleme veškeré .emit změny. output.value = (<Slider>e.target).value; // upravujeme typ objektů aby vše fungovalo. jak má});input.listenEvent("valueChanged", function(e) { //pokud nám příjde z input nějaká hodnota, prostě jí dosadíme do slideruif (!slider.isMoving) { slider.value =input.value; }});context.configProperties.listenEvent("valueChanged", setupSliderFromProperties); //nastavování změn