MontyPan / T-Chou

殺人放火金腰帶,修橋補路無屍骸(?)
0 stars 1 forks source link

GF-draw:滑鼠游標移到某個物件上,顯示另一個物件。 #38

Open cwchou2016 opened 4 years ago

cwchou2016 commented 4 years ago

請問一下,GF-draw 有辦法讓滑鼠游標移到某個物件上,然後顯示另一個物件嗎?

有點像是 FTL 下面那條顯示資訊的 rectangle,不過這個 rectangle 在滑鼠移開的時候整個物件會消失,而且位置不固定(根據游標的位置決定顯示位置)。

MontyPan commented 4 years ago

先解決一下作文問題,順便夾帶一些背景知識,畢竟要搞畫面的話,這些搞清楚還是比較好的:

請問一下,GF-draw 有辦法讓滑鼠游標移到某個物件上,然後顯示另一個物件嗎?

在這裡用「物件」會讓人很恐慌,尤其你又是在寫 Java...... XD。用 sprite(基本上電腦繪圖領域可能都通用)或是 GF-Draw 裡頭的 Layer 都比較好。當然如果 GF-Draw 是一個 physical engine 說不定講 object 又沒問題(甚至是必要的 XD)。只能說在資訊領域的名詞定義實在很痛苦......

「游標 / 鼠標(對岸用法)移到某個物件上」這個可以直接說「mouse over」,基本上這也是通用詞彙了

你描述的這個需求,常見的學名是 tooltip、但是講 popup 其實也可以,這兩個區別就(尤其是網頁環境)底層來說區別不是很大。因為撇開一些 HTML tag 內建的功能(例如 <a>title),要能有統一的外觀跟程式控制能力,通常都是 UI library 另外畫出來然後控制它的出現時機以及位置(包括漂浮在最上層)。不過呢,如果講究一點的話,通常會有這些區別:

順便扯一個有點遙遠,但其實也常常(跟 popup)混在一起(因為還是那個原因,底層區別不大)就是 modal / dialog。modal 通常還具備了「modal 底下的東西都不給使用者操作」的特性,很多時候 modal 也被當成 dialog 的屬性來用...... blahblah

如果你看完這一堆還沒昏的話,再給你最後一擊 :dancer: ,就是各家 UI component / library provider 的名詞定義都不太一樣,上面講得就只是個通俗的概念,不一定能對應到各家的命名....... [死]

以下「顯示另一個物件」的這玩意,我先用「tooltip」這個字。


好,終於要來回答你的問題了,答案是:好像沒辦法...... :skull: 這也是為什麼我那個 ftl.html 會讓他卡在最下面的原因...... (艸

更仔細地說,應該這樣講:「沒辦法讓 tooltip 的 layer / sprite 都屬於同一個 LayerContainer。」

其實學理上應該是可行的(GXT 的 chart 好像也有類似功能),就是 onSpriteOver() 的時候把 tooltip 作 Layer.setHidden(false) 然後 onSpriteLeave() 的時候 setHidden(true),但是實際上 tooltip 那個 layer 會一直閃爍還是消失之類的狀況(忘記細節 XD),總之無法正常顯示。當時有點懶得去抓這問題...... Orz(謎之聲:難道現在就不懶的意思 [飛踢]),應該是 LayerContainer.handleEvent() 裡頭塞了一個 redrawSurface() 炸的問題....... (ry

這裡先提供一個(理論上的) workaround,就是另外生一個 Popup 來顯示,因為那是獨立於 LayerContainer 的 widget,所以不會受到影響,但相對的就得生出另一個 GXT Component 來塞到 Popup 裡頭去.........

暫時先這樣....... [逃]

MontyPan commented 4 years ago

後來找到解法、後來想要寫成 API、後來又發現這事情很大條短時間寫不完....... (ry

所以先講很受局限的解法:

public class WTF extends LayerContainer {
    public WTF() {
        TextButton textbutton = new TextButton("over me!");
        textbutton.addSpriteOverHandler(new SpriteOverHandler() {
            @Override
            public void onSpriteOver(SpriteOverEvent event) {
                Event be = event.getBrowserEvent();
                tooltip.setHidden(false);
                tooltip.setLX(be.getClientX() - WTF.this.getAbsoluteLeft() + 10);
                tooltip.setLY(be.getClientY() - WTF.this.getAbsoluteTop() + 10);
            }
        });
        textbutton.addSpriteOutHandler(new SpriteOutHandler() {
            @Override
            public void onSpriteLeave(SpriteOutEvent event) {
                tooltip.setHidden(true);
            }
        });

        //一些設定位置大小等等的細節就略
        //總之記得把 textbutton / tooltip 加到 WTF 上頭去
        //注意,`tooltip` 必須直接加在 LayerContainer 上,不然位置會出問題
    }
}

class Tooltip extends LayerSprite {
    Tooltip() {
        LCircleSprite circle = new LCircleSprite(10);
        circle.setFill(RGB.BLUE);
        circle.setLX(30);
        circle.setLY(30);
        add(circle);

        setBgColor(RGB.YELLOW);
        setLZIndex(30000);
        setHidden(true);
        resize(200, 80);
    }
}

之前會出現閃爍 / 消失的問題,其實是滑鼠(在短時間內)移動到剛剛出現的 tooltip 上,程式會觸發 textButton 的 onSpriteLeave(),然後觸發 tooltip 的 onSpriteOver()(這裡是簡化後的講法,實際上會囉唆一點 Orz)。所以最簡單的方法就是加上個 offset(例如上面的 +10),這樣滑鼠就很難在短時間內移動到還未消失的 tooltip 上。也許更確實的方法是在 textbutton.onSpriteLeave() 把 tooltip 的位置移到很遠的地方,不過還沒測就是了。

至於要寫成 API,目前還在便秘當中。這牽扯的問題有點多,光「是 LayerSprite 才提供 setTooltip()、還是 LSprite 都提供 setTooltip()」就讓我傷透腦筋,所以先 FYI 一下 有個交代。

畢竟你現在是 GF-Draw 的最大客戶

cwchou2016 commented 4 years ago

最近試了一下,你的範例顯示不出來, 不過把 TooltipsetLZindex() 改成 0 的話就可以。

是不是 hide 的屬性只是讓 Sprite 不顯示,但是還是存在在位置上?

MontyPan commented 4 years ago

O.o 我的測試程式碼還在,除了是 gf-test 的架構之外,其他(至少 Tooltip 的部份)是一樣的,所以我不太確定為啥你的得改那樣才行(而且 z-index 設成 0 這超級不合理的阿)

至於你說的問題,答案是:是,最終就只是把 svg 的 attribute 改掉,其他值都不會變。(CSS 大抵上也通常是類似作法)。不過理論上應該不會因此觸發相關 event 才對。


我覺得你還是把時間花在「你希望看到什麼 API」這上頭。當然你要繼續尋根究底那也行,開個 gist 或是 repo 給我一下你炸掉的 code。

cwchou2016 commented 4 years ago

z-index 設定為 0 ,讓 tooltip 設定在最下面,有需要顯示的時候再把它移上來,要不然 tooltip 會擋住觸發的 sprite。

此外,我遇到的另一個問題是,我把 tooltip extend VerticalLayoutLayer ,在 onSpriteLeave()被觸發之後,會有部分的 sprite 不會被 hide。

程式碼如下: https://gist.github.com/cwchou2016/654636243a121d86071207c5fd1d33e7

裡面的 ExhibitionInfoLayerSprite 就是 tooltip

MontyPan commented 4 years ago

那個 bg 仍然會顯示在化面上,還真他 X 的是個謎,我完全無法解釋,這很有可能是 GXT 底層的 bug....... :scream:

我現在終於瞭解你說的那個 z-index 導致 line 無法觸發的問題,這的確是之前沒遇過也沒想過的問題。不過呢,我的解法不會是用 z-index,主要原因是效能問題(當然你踩出來的那個雷會是很重要的支持理由 lol)。

z-index 應該會造成較多的計算(至於是 JS 還是 browser rendering engine.... 這部份細節略,應該都有)。z-index 不是一個實際存在 svg 當中的屬性。佐以觀察 DOM 結構的結果來說(aka 這不是 source code 的定論,可能會有例外 XD),GXT 會根據 z-index 重新調整 svg 當中的 element 先後順序,整個看起來就不是什麼快樂的事情,尤其是 tooltip 上頭的東西很多的時候。

所以我會這樣搞......

//in Chart 的 constructor
//除了排整齊一點之外,就是調回我原本的 z-index 寫法
//跟(重點)tooltip.setLX() / setLY()
        Line line = new Line(100, 1000);
        addLayer(line);

        tooltip.setLZIndex(3000);  //游標移到 sprite 上再把 tooltip 移到最前面
        tooltip.setInfo("test", "adfadfadf");
        tooltip.setLX(-10000);  //你指不到我~你指不到我~ [達叔調]
        tooltip.setLY(-10000);
        addLayer(tooltip);

        line.addSpriteOverHandler(new SpriteOverEvent.SpriteOverHandler() {
            @Override
            public void onSpriteOver(SpriteOverEvent event) {
                Event be = event.getBrowserEvent();
                tooltip.setLX(be.getClientX() - Chart.this.getAbsoluteLeft() + 10);
                tooltip.setLY(be.getClientY() - Chart.this.getAbsoluteTop() + 10);
                tooltip.setHidden(false);
            }
        });
        line.addSpriteOutHandler(new SpriteOutEvent.SpriteOutHandler() {
            @Override
            public void onSpriteLeave(SpriteOutEvent event) {
                tooltip.setHidden(true);
            }
        });

這樣就不會動到 z-index 值,而且反正 tooltip 的位置本來就是要調整的.....

gist 那邊我也有寫一些 comment,記得過去看......

MontyPan commented 4 years ago

我最近在開新的 副本亂搞,因為總總因素覺得上面取得 event 座標的方式太 白痴 麻煩囉唆又不保險,前端工程師不太可能到現在還容忍這種事情,所以仔細看一下 event 有沒有什麼其他資訊可以使用...... 還真的有...... 就是 layerX, layerY。各種 X, Y 的差別可以看這篇(這篇看起來挺嚴謹的,應該不會再被陰.....)

不過呢.... 這玩意在 GWT 的 API 沒有提供(主要大概是因為不是 standard 沒有 cross-browser 所以選擇不實做?)。所以就只好自己來......

    public static native double eventLayerX(NativeEvent evt) /*-{
        return evt.layerX || 0;
    }-*/;

    public static native double eventLayerY(NativeEvent evt) /*-{
        return evt.layerY || 0;
    }-*/;

如此一來,改寫我上一個 comment 的 code 就會變成

                Event be = event.getBrowserEvent();
                tooltip.setLX(eventLayerX(be) + 10);
                tooltip.setLY(eventLayerY(be) + 10);
                tooltip.setHidden(false);

那兩個 utility method 應該會在 遙遠的 未來寫進 GF 裡頭, 趕快過來報大腿誠心許願就會早點上阿 [指], 你如果有需要可以自己先弄一個頂一下 XD

MontyPan commented 4 years ago

我剛剛又炸了一輪 sprite 的 mouse over / out 的問題,因為這次功能更單純,所以測試的也更詳細,還整理了一個 SSCCE 在這裡: https://gist.github.com/MontyPan/892d20ceb046740ee87f25332dc2433c

(恩... 這可能不夠 SSCCE,因為還用了 GF-draw,真要搞就要弄純 GXT 的版本.... 😱 )

用比較正式的講法,當兩個 LayerSprite A、B 同時符合兩個條件:

就有機率(移動緩慢時基本上 100%)出現滑鼠從 A 移動到 B 的時候,不會出現 A 的 SpriteOutEvent,反而會出現 B 的 SpriteOutEvent

目前看起來,這不是 GF-draw 機制出的問題,而是 GXT 本來的行為就這樣... 理論上 GF 可能也不會幫 GXT 做 workaround...... 🙈

我記得你在超前部屬 tooltip 的時候靠腰過 tooltip 不會消失的問題(我現在還是沒打算理你那段啦... [被毆飛]),你可以看一下是不是同樣是這個 pattern,說不定會讓你開心 / 對 GF 有信心一點點 XDDD

其實我只是要找個地方作筆記而已