Reactで超簡単、タグで挟み込むだけの仮想ウインドウの実装

# Reactで超簡単、タグで挟み込むだけの仮想ウインドウの実装

## １．今回のブツ

![キャプチャ](https://raw.githubusercontent.com/JavaScript-WindowFramework/jwf-react-sample01/ScreenShot/ScreenShot.gif)

## ２．仮想ウインドウとは

　Webコンテンツを表示する際、そのコンテンツを表示しているアプリケーションは、モバイルも含めてウインドウ単位でUIが構成されています。デスクトップ用のOSはマルチウインドウ、モバイルの場合はシングルウインドウを切り替えていく方式になっています。

　今回は仮想ウインドウということで、ブラウザのWebコンテンツを表示する領域に仮想的なウインドウを作ります。この仮想ウインドウがあると、流れの異なる入出力を同時に表示させることが出来ます。その他、突発的なデータ入力の際、入力フォームを埋め込む位置をレイアウトの中に用意しなくて済むようになります。

## ３．仮想ウインドウを作る上で考えるべき事

　PC用のOSのようなマルチウインドウシステムは、実際に実現しようとするといくつか面倒な部分があります。マウスやタップイベントの処理、イベントが発生したウインドウとそのパーツの把握、ウインドウの重ね合わせ順序や位置とサイズ、処理する内容はそこそこな量になります。さらにウインドウの中に子ウインドウを埋め込む場合の処理や、どれがアクティブウインドウでどれを非アクティブにしなければならないのか、実際に組んでみると、マルチウインドウシステムでは大変な作業が行われているのです。

　ちなみにWindows3.1時代はCPUが貧弱、しかもウインドウシステムの出来も良くなかったので、マウスをぐるぐると移動させるだけで、あっという間にCPU使用率100%に達するような状態でした。ウインドウシステムを組む上では、こういった負荷の部分も気にしながら実装する必要があります。

## ４．Reactでの実装

　独自の仮想ウインドウフレームワークは [npm](https://www.npmjs.com/package/@jswf/core) に上げてありますが、今回はReact用で簡単に使えるものを作ることが目標です。タグで囲むだけで自動的に仮想ウインドウになったら便利ですよね。目標はこれです。

```tsx:index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { JSWFWindow } from "@jswf/react";

function App() {
  return (
    <>
      <JSWFWindow>
        この中に入れたコンテンツが仮想ウインドウ上に表示される
      </JSWFWindow>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById("root") as HTMLElement);
```

　はい、仮想ウインドウが表示されました。目標達成です。

## ５．React用仮想ウインドウコンポーネント

　作ろうと思い立って一週間、最低限の動作はするようになったので紹介します。

### インストール方法

```.sh
npm -D i @jswf/react
```

### 使い方

```html:.tsx
<JSWFWindow>挟み込む</JSWFWindow>
```

以上です。

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267629/75c9fc33-21e0-d907-18e5-b39e21f5529e.png)

### Propsパラメータ

現時点でこんな感じのパラメータが設定できます

| Name        | Type               | Info                                                       |
| ----------- | ------------------ | ---------------------------------------------------------- |
| x           | number &#124; null | X Position                                                 |
| y           | number &#124; null | Y Position                                                 |
| width       | number             | Width                                                      |
| height      | number             | Height                                                     |
| moveable    | boolean            | trueだとクライアント領域のドラッグで移動                      |
| borderSize  | number             | サイズ変更用の見えないフレームのサイズ                        |
| titleSize   | number             | タイトルバーのサイズ                                         |
| title       | string             | タイトル                                                    |
| active      | boolean            | trueでアクティブ                                            |
| overlapped  | boolean            | falseにするとウインドウが親ウインドウ内にのみ表示              |
| windowStyle | number             | WindowStyle ビットの込み合わせ <br> TITLE:タイトルバー<br> MAX:最大化ボタン<br> MIN:最小化ボタン<br> CLOSE:クローズボタン<br> FRAME:枠の表示<br> RESIZE:サイズ変更<br> |
| windowState | WindowState                    | WindowState　ウインドウの状態<br>  NORMAL:通常<br> MAX:最大化<br> MIN:最小化<br> HIDE:非表示<br> |
| onUpdate    | function(p:WindowInfo)  &#124; null | ウインドウの状態が変化するとコールバックされる |

### メソッド

- foreground()  
  - ウインドウをフォアグラウンドにする
- setWindowState(state: WindowState | undefined)  
  - ウインドウの状態を変更する  
  - state  
     - WindowState.NORMAL  
     - WindowState.MAX  
     - WindowState.MIN  
     - WindowState.HIDE  

## ６．サンプルソースの紹介

　createRefでコンポーネントのインスタンスを受け取るようにすれば、直接操作することも可能です。

```tsx:index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { JSWFWindow, WindowState, WindowStyle, WindowInfo } from "@jswf/react";

function App() {
  const frame = React.createRef<JSWFWindow>();
  const [info, setInfo] = React.useState<WindowInfo | null>(null);
  return (
    <>
      <JSWFWindow ref={frame} title="Window1" x={50} y={100}>
        この中に入れたコンテンツは仮想ウインドウ上に表示されます
      </JSWFWindow>

      <JSWFWindow title="Window2" width={600} height={500} windowStyle={~WindowStyle.CLOSE}>
        ウインドウ位置を設定しなかった場合、中央に表示されます
        <br />
        windowStyleで使用する機能を設定できます

        <JSWFWindow title="ChildWindow" overlapped={false} width={200} height={200}>
          overlappedをfalseにするとクライアント領域内に表示され、trueにすると重ね合わせだけ調整されます
        </JSWFWindow>

      </JSWFWindow>

      <JSWFWindow title="更新テスト" y={50} onUpdate={p => setInfo(p)}>
        <pre>{info && JSON.stringify(info,["realX","realY","realWidth","realHeight"],' ')}</pre>
      </JSWFWindow>

      <button onClick={() => {
          frame.current!.foreground();
          frame.current!.setWindowState(WindowState.NORMAL);
        }}>
        Window1を復活させる
      </button>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById("root") as HTMLElement);
```

## ７．関連リンク

- コンポーネントのソースコード  
  - [https://github.com/JavaScript-WindowFramework/jswf-react](https://github.com/JavaScript-WindowFramework/jswf-react)

- コンポーネントを利用したサンプルソース  
  - [https://github.com/JavaScript-WindowFramework/jwf-react-sample01](https://github.com/JavaScript-WindowFramework/jwf-react-sample01)

- 実際の動作  
  - [https://javascript-windowframework.github.io/jwf-react-sample01/dist/](https://javascript-windowframework.github.io/jwf-react-sample01/dist/)

## ８．まとめ

　Reactはそこそこ前にちょっと触った程度だったので、とりあえずコンポーネントを作ることから始めました。そろそろ入門用のチュートリアルでもやろうかと思っています。

　仮想ウインドウコンポーネントに関しては、自分の開発でも必要になるのでこれから機能を増やしていこうと思っています。なにか要望があれば、コメントをください。