From 021d22299d9863aa387a5ac30cfd1fd025cfe848 Mon Sep 17 00:00:00 2001 From: SoraKumo Date: Sat, 7 Sep 2019 19:21:03 +0900 Subject: [PATCH] =?UTF-8?q?=E5=8B=95=E7=9A=84=E5=88=86=E5=89=B2=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E4=BD=9C=E6=88=90=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + images/resize.svg | 19 +++ src/splitView/index.tsx | 300 +++++++++++++++++++++++++--------- src/splitView/parts/Bar.tsx | 103 ++++++++++++ src/splitView/parts/Child.ts | 16 ++ src/splitView/parts/Resize.ts | 30 ++++ src/splitView/parts/Root.ts | 109 ++++++++++++ src/window/index.tsx | 99 +++++------ 8 files changed, 554 insertions(+), 123 deletions(-) create mode 100644 images/resize.svg create mode 100644 src/splitView/parts/Bar.tsx create mode 100644 src/splitView/parts/Child.ts create mode 100644 src/splitView/parts/Resize.ts create mode 100644 src/splitView/parts/Root.ts diff --git a/README.md b/README.md index 96e2220..301733c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ ReactDOM.render(, document.getElementById("root") as HTMLElement); | overlapped | boolean | falseにするとウインドウが親ウインドウ内にのみ表示 | | windowStyle | number | WindowStyle ビットの込み合わせ
TITLE:タイトルバー
MAX:最大化ボタン
MIN:最小化ボタン
CLOSE:クローズボタン
FRAME:枠の表示
RESIZE:サイズ変更
| | windowState | WindowState | WindowState ウインドウの状態
NORMAL:通常
MAX:最大化
MIN:最小化
HIDE:非表示
| +| clientStyle | React.CSSProperties | クライアント領域に適用するスタイル | | onUpdate | function(p:WindowInfo) | null | ウインドウの状態が変化するとコールバックされる | ### 4.3 メソッド diff --git a/images/resize.svg b/images/resize.svg new file mode 100644 index 0000000..6f6c6e3 --- /dev/null +++ b/images/resize.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/src/splitView/index.tsx b/src/splitView/index.tsx index 7977732..4979b5c 100644 --- a/src/splitView/index.tsx +++ b/src/splitView/index.tsx @@ -1,103 +1,244 @@ -import { Component, createRef } from "react"; -import React from "react"; - -import styled from "styled-components"; +import ResizeObserver from "resize-observer-polyfill"; +import React, { Component, createRef, ReactNode } from "react"; import { Manager, JWFEvent, MovePoint } from "../lib/Manager"; +import { Root } from "./parts/Root"; +import { Bar } from "./parts/Bar"; +import { Child } from "./parts/Child"; + +export type SplitType = "ns" | "sn" | "we" | "ew"; -interface StyleProps { - type?: 'ns' | 'sn' | 'we' | 'ew'; +interface SplitProps { + type?: SplitType; pos?: number; + activeSize?: number; + barSize?: number; + children?: ReactNode | null; + style?: React.CSSProperties; } -export const Root = styled.div.attrs(p => ({ - style: { - } -})) ` -& { - position:absolute; - left:0; - right:0; - top:0; - bottom:0; - background-color:blue; -} - ` -interface BarProps { +/** + *画面分割コンポーネント + * + * @export + * @class SplitView + * @extends {Component} + */ + +interface State { pos: number; - size: number; - type: 'ns' | 'sn' | 'we' | 'ew'; + activeMode: boolean; + barOpen: boolean; } -export const Bar = styled.div.attrs(p => { - let style = {}; - switch (p.type) { - case 'we': - style = { - top: 0, - bottom: 0, - left: p.pos + 'px', - width: p.size + 'px', - cursor: 'ew-resize' - } - break; - case 'ew': - style = {} - break; - case 'ns': - style = {} - break; - case 'sn': - style = {} - break; - } - return { style }; -}) ` - - position:absolute; - box-sizing: border-box; - background-color: #777777; - border: outset 2px #b8b7b7; - user-select: none; - ` -export class SplitView extends Component { - splitterRef = createRef(); - barType: 'ns' | 'sn' | 'we' | 'ew' = 'we'; - constructor(props: StyleProps) { +export class SplitView extends Component { + static defaultProps = { + type: "we", + barSize: 8, + pos: 100, + activeSize: 300, + style: {} + }; + private closeHandle?: number; + private resizeObserver?: ResizeObserver; + private rootRef = createRef(); + private splitterRef = createRef(); + private childRef = [createRef(), createRef()]; + private children: (ReactNode | undefined)[] = [undefined, undefined]; + public constructor(props: SplitProps) { super(props); - if (props.type) - this.barType = props.type; - - this.state = { pos: props.pos !== undefined ? props.pos : 100 }; - + this.state = { pos: props.pos!, activeMode: false, barOpen: true }; + if (props.children) { + if (props.children instanceof Array) { + this.children = props.children; + } + } } - render() { + public render() { return ( - - - ) + + {this.children[1]} + {this.children[0]} + this.onOpen(open)} + > + + ); + } + componentDidUpdate() { + this.onLayout(); } public componentDidMount() { const node = this.splitterRef.current!; node.addEventListener("move", this.onMove.bind(this)); + + this.resizeObserver = new ResizeObserver(() => { + this.onLayout(); + }); + this.resizeObserver.observe(this.rootRef.current! as Element); + + this.onLayout(); } public componentWillUnmount() { const node = this.splitterRef.current!; node.removeEventListener("move", this.onMove.bind(this)); + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = undefined; + } } - onMove(e: JWFEvent) { + protected onMove(e: JWFEvent) { let p = e.params as MovePoint; let pos = this.state.pos; - switch (this.barType) { + switch (this.props.type!) { case "we": pos = p.nodePoint.x + p.nowPoint.x - p.basePoint.x; break; + case "ew": + pos = p.nodePoint.x - (p.nowPoint.x - p.basePoint.x); + break; + case "ns": + pos = p.nodePoint.y + p.nowPoint.y - p.basePoint.y; + break; + case "sn": + pos = p.nodePoint.y - (p.nowPoint.y - p.basePoint.y); + break; } - console.log(pos); this.setState({ pos }); } - onMouseDown(e: - | React.MouseEvent - | React.TouchEvent) { + closeBar() { + if (this.closeHandle) { + clearTimeout(this.closeHandle); + } + setTimeout(() => { + // this.onOpen(false); + this.closeHandle = undefined; + }, 2000); + } + protected onLayout() { + let activeMode = false; + let pos = this.state.pos; + const rootRef = this.rootRef!.current!; + const children = [this.childRef[0].current!, this.childRef[1].current!]; + const barSize = this.props.barSize!; + const barType = this.props.type; + const width = rootRef.offsetWidth; + const height = rootRef.offsetHeight; + + if (barType === "we" || barType === "ew") { + const w = width - (pos + barSize); + if (w < this.props.activeSize!) { + activeMode = true; + if (!this.state.activeMode) { + children[1].style.left = "0"; + children[1].style.right = "0"; + children[1].style.width = null; + children[1].style.height = null; + children[1].style.top = "0"; + children[1].style.bottom = "0"; + this.closeBar(); + } + } + } else { + const h = height - (pos + barSize); + if (h < this.props.activeSize!) { + activeMode = true; + if (!this.state.activeMode) { + children[1].style.left = "0"; + children[1].style.right = "0"; + children[1].style.top = "0"; + children[1].style.bottom = "0"; + children[1].style.width = null; + children[1].style.height = null; + this.closeBar(); + } + } + } + if (activeMode !== this.state.activeMode) { + if (!activeMode) { + this.onOpen(true); + } + this.setState({ activeMode }); + } + + switch (barType) { + case "we": + children[0].style.left = "0"; + children[0].style.right = null; + children[0].style.width = pos + "px"; + children[0].style.height = null; + children[0].style.top = "0"; + children[0].style.bottom = "0"; + if (!activeMode) { + children[1].style.left = pos + barSize + "px"; + children[1].style.right = "0"; + children[1].style.width = null; + children[1].style.height = null; + children[1].style.top = "0"; + children[1].style.bottom = "0"; + } + break; + case "ew": + children[0].style.left = null; + children[0].style.right = "0"; + children[0].style.width = pos + "px"; + children[0].style.height = null; + children[0].style.top = "0"; + children[0].style.bottom = "0"; + if (!activeMode) { + children[1].style.left = "0"; + children[1].style.right = pos + barSize + "px"; + children[1].style.width = null; + children[1].style.height = null; + children[1].style.top = "0"; + children[1].style.bottom = "0"; + } + break; + case "ns": + children[0].style.top = "0"; + children[0].style.bottom = null; + children[0].style.width = null; + children[0].style.height = pos + "px"; + children[0].style.left = "0"; + children[0].style.right = "0"; + if (!activeMode) { + children[1].style.top = pos + barSize + "px"; + children[1].style.bottom = "0"; + children[1].style.width = null; + children[1].style.height = null; + children[1].style.left = "0"; + children[1].style.right = "0"; + } + break; + case "sn": + children[0].style.top = null; + children[0].style.bottom = "0"; + children[0].style.width = null; + children[0].style.height = pos + "px"; + children[0].style.left = "0"; + children[0].style.right = "0"; + if (!activeMode) { + children[1].style.top = "0"; + children[1].style.bottom = pos + barSize + "px"; + children[1].style.width = null; + children[1].style.height = null; + children[1].style.left = "0"; + children[1].style.right = "0"; + } + break; + } + } + protected onMouseDown( + e: + | React.MouseEvent + | React.TouchEvent + ) { if (Manager.moveNode == null) { const node = this.splitterRef.current!; Manager.moveNode = node; @@ -113,4 +254,15 @@ export class SplitView extends Component { e.preventDefault(); } } -} \ No newline at end of file + onOpen(open: boolean) { + const children = this.childRef[0].current!; + if (open) { + children.style.animation = + this.props.type + "DrawerShow 0.5s ease 0s forwards"; + } else { + children.style.animation = + this.props.type + "DrawerClose 0.5s ease 0s forwards"; + } + this.setState({ barOpen: open }); + } +} diff --git a/src/splitView/parts/Bar.tsx b/src/splitView/parts/Bar.tsx new file mode 100644 index 0000000..78e2468 --- /dev/null +++ b/src/splitView/parts/Bar.tsx @@ -0,0 +1,103 @@ +import styled from "styled-components"; +import React, { ReactNode, Component, RefObject } from "react"; +import { Resize } from "./Resize"; +import imgResize from "../../../images/resize.svg"; + +interface BarProps extends React.HTMLAttributes { + pos: number; + size: number; + children?: ReactNode | null; + type: "ns" | "sn" | "we" | "ew"; + refs: RefObject; + open: boolean; + activeMode:boolean; + procOpen:(open:boolean)=>void +} +const BarStyle = styled.div.attrs(p => { + let style = {}; + switch (p.type) { + case "we": + style = { + top: 0, + bottom: 0, + right: null, + left: p.pos + "px", + width: p.size + "px", + cursor: "ew-resize", + paddingLeft: "2px" + }; + break; + case "ew": + style = { + top: 0, + bottom: 0, + left: null, + right: p.pos + "px", + width: p.size + "px", + cursor: "ew-resize", + paddingLeft: "2px" + }; + break; + case "ns": + style = { + left: 0, + right: 0, + top: p.pos + "px", + bottom: null, + height: p.size + "px", + paddingTop: "px", + cursor: "ns-resize" + }; + break; + case "sn": + style = { + left: 0, + right: 0, + top: null, + bottom: p.pos + "px", + height: p.size + "px", + paddingTop: "px", + cursor: "ns-resize" + }; + break; + } + return { style }; +})` + position: absolute; + overflow: visible; + box-sizing: border-box; + background-color: #777777; + border: outset 2px #b8b7b7; + user-select: none; + vertical-align: middle; +`; + + +interface BarState { + open: boolean; +} +export class Bar extends Component { + constructor(props: BarProps) { + super(props); + this.state = { open: true }; + } + componentDidUpdate() { + if(!this.props.open && this.state.open){ + const node = this.props.refs.current!; + node.style.animation = this.props.type + "DrawerClose 0.5s ease 0s forwards"; + this.setState({open:false}); + }else if(this.props.open && !this.state.open){ + const node = this.props.refs.current!; + node.style.animation = this.props.type + "DrawerShow 0.5s ease 0s forwards"; + this.setState({open:true}); + } + } + render() { + return ( + + {this.props.activeMode && this.props.procOpen(!this.state.open)}/>} + + ); + } + +} diff --git a/src/splitView/parts/Child.ts b/src/splitView/parts/Child.ts new file mode 100644 index 0000000..9baeee4 --- /dev/null +++ b/src/splitView/parts/Child.ts @@ -0,0 +1,16 @@ +import { ReactNode } from "react"; +import styled from "styled-components"; + +interface StyleProps { +} + +export const Child = styled.div.attrs(p => ({ + style: { + } +})) ` +& { + position:absolute; + overflow:hidden; + background-color: rgba(255,255,255,0.8); +} + ` \ No newline at end of file diff --git a/src/splitView/parts/Resize.ts b/src/splitView/parts/Resize.ts new file mode 100644 index 0000000..42128e8 --- /dev/null +++ b/src/splitView/parts/Resize.ts @@ -0,0 +1,30 @@ +import { ReactNode } from "react"; +import styled from "styled-components"; + +interface StyleProps { + size:number; +} + +export const Resize = styled.img.attrs(p => ({ + style: { + } +})) ` +& { + margin: auto; + position:relative; + width:${p=>p.size}px; + height:${p=>p.size}px; + margin-top:-${p=>p.size/2+2}px; + margin-left:-${p=>p.size/2+2}px; + top:50%; + left:50%; + cursor:pointer; + overflow:visible; + background-color:rgba(255,255,255,0.8); + border:solid 1px rgba(0,0,0,0.4); + border-radius:4px; + &:hover{ + background-color:rgba(200,200,255,0.8);; + } +} + ` \ No newline at end of file diff --git a/src/splitView/parts/Root.ts b/src/splitView/parts/Root.ts new file mode 100644 index 0000000..b63908e --- /dev/null +++ b/src/splitView/parts/Root.ts @@ -0,0 +1,109 @@ +import { ReactNode } from "react"; +import styled from "styled-components"; + +interface StyleProps { + type?: "ns" | "sn" | "we" | "ew"; + pos?: number; + children?: ReactNode | null; +} + +export const Root = styled.div.attrs(p => ({ + style: {} +}))` + & { + overflow: hidden; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + } + + @keyframes nsDrawerShow { + 0% { + top: 0; + transform: translateY(-100%); + } + + 100% { + transform: translateY(0); + } + } + + @keyframes nsDrawerClose { + 0% { + transform: translateY(0); + } + + 100% { + top: 0; + transform: translateY(-100%); + } + } + + @keyframes snDrawerShow { + 0% { + bottom: 0; + transform: translateY(100%); + } + + 100% { + transform: translateY(0); + } + } + + @keyframes snDrawerClose { + 0% { + transform: translateY(0); + } + + 100% { + bottom: 0; + transform: translateY(100%); + } + } + + @keyframes weDrawerShow { + 0% { + left: 0; + transform: translateX(-100%); + } + + 100% { + transform: translateX(0); + } + } + + @keyframes weDrawerClose { + 0% { + transform: translateX(0); + } + + 100% { + left: 0; + transform: translateX(-100%); + } + } + + @keyframes ewDrawerShow { + 0% { + transform: translateX(100%); + right: 0; + } + + 100% { + transform: translateX(0); + } + } + + @keyframes ewDrawerClose { + 0% { + transform: translateX(0); + } + + 100% { + right: 0; + transform: translateX(100%); + } + } +`; diff --git a/src/window/index.tsx b/src/window/index.tsx index 8c5bd43..2a90093 100644 --- a/src/window/index.tsx +++ b/src/window/index.tsx @@ -1,10 +1,10 @@ import ResizeObserver from "resize-observer-polyfill"; import React, { ReactNode, Component, createRef } from "react"; import { Manager, MovePoint, JWFEvent } from "../lib/Manager"; -import { Clinet } from "./Parts/Client"; -import { Title } from "./Parts/Title"; -import { Root } from "./Parts/Root"; -import { Border, borders } from "./Parts/Border"; +import { Clinet } from "./parts/Client"; +import { Title } from "./parts/Title"; +import { Root } from "./parts/Root"; +import { Border, borders } from "./parts/Border"; export enum WindowStyle { TITLE = 1, @@ -29,6 +29,7 @@ export interface WindowProps { windowStyle?: number; windowState?: WindowState; onUpdate?: ((status: WindowInfo) => void) | null; + clientStyle?:React.CSSProperties; } type NonNullableType = { [P in K]-?: T[P]; @@ -79,6 +80,23 @@ interface MoveParams { * @extends {Component} */ export class JSWFWindow extends Component { + static defaultProps: WindowProps = { + x: null, + y: null, + width: 300, + height: 300, + moveable: false, + borderSize: 8, + titleSize: 32, + title: "", + active: false, + overlapped: true, + windowStyle: 0xff, + windowState: WindowState.NORMAL, + clientStyle:{}, + onUpdate: null + }; + private rootRef = createRef(); private titleRef = createRef(); private clientRef = createRef(); @@ -97,45 +115,26 @@ export class JSWFWindow extends Component { public constructor(props: WindowProps) { super(props); let state: State; - if (props) { - state = { - active: props.active || false, - overlapped: props.overlapped == null ? true : props.overlapped, - titlePrmisson: - props.windowStyle !== undefined ? props.windowStyle : 0xff, - titleSize: - (props.windowStyle && props.windowStyle & WindowStyle.TITLE) === 0 - ? 0 - : props.titleSize || 32, - borderSize: props.borderSize || 8, - x: props.x === undefined ? null : props.x, - y: props.y === undefined ? null : props.y, - width: props.width || 300, - height: props.height || 300, - oldEnumState: WindowState.HIDE, - windowState: props.windowState || WindowState.NORMAL, - boxEnumState: WindowState.HIDE, - clientWidth: 0, - clientHeight: 0 - }; - } else { - state = { - active: false, - overlapped: true, - titlePrmisson: 0xff, - titleSize: 32, - borderSize: 8, - x: null, - y: null, - width: 300, - height: 300, - oldEnumState: WindowState.HIDE, - windowState: WindowState.NORMAL, - boxEnumState: WindowState.HIDE, - clientWidth: 0, - clientHeight: 0 - }; - } + + state = { + active: props.active!, + overlapped: props.overlapped!, + titlePrmisson:props.windowStyle!, + titleSize:(props.windowStyle! & WindowStyle.TITLE) === 0 + ? 0 + : props.titleSize!, + borderSize: props.borderSize!, + x: props.x!, + y: props.y!, + width: props.width!, + height: props.height!, + oldEnumState: WindowState.HIDE, + windowState: props.windowState!, + boxEnumState: WindowState.HIDE, + clientWidth: 0, + clientHeight: 0 + }; + this.state = state; this.windowInfo = { @@ -143,18 +142,19 @@ export class JSWFWindow extends Component { y: state.y, width: state.width, height: state.height, - moveable: (props && props.moveable) || false, + moveable: props.moveable!, borderSize: state.borderSize, - title: (props && props.title) || "", + title: props.title!, titleSize: state.titleSize, children: (props && props.children) || null, active: state.active, overlapped: state.overlapped, - windowStyle: (props && props.windowStyle) || 0xff, + windowStyle: props.windowStyle!, windowState: state.windowState, - onUpdate: (props && props.onUpdate) || null, + onUpdate: props.onUpdate!, clientWidth: 0, clientHeight: 0, + clientStyle:props.clientStyle!, realX: 0, realY: 0, realWidth: 0, @@ -201,7 +201,7 @@ export class JSWFWindow extends Component { node.removeEventListener("active", this.onActive.bind(this)); } } - componentDidUpdate() { + public componentDidUpdate() { if (this.props.onUpdate) { let flag = false; for (const key of Object.keys(this.windowInfo)) { @@ -415,6 +415,7 @@ export class JSWFWindow extends Component { TitleSize={this.state.titleSize} Width={clientWidth} Height={clientHeight} + style={this.props.clientStyle!} > {this.props.children} @@ -714,7 +715,7 @@ export class JSWFWindow extends Component { width: pwidth }; if (!this.moveHandle) { - this.moveHandle=setTimeout(() => { + this.moveHandle = setTimeout(() => { this.setState(this.moveParams!); this.moveHandle = undefined; }, 10);