from typing import Optional, List, Callable, SupportsFloat, Union, Dict
from copy import deepcopy
import webcface.field
import webcface.text
import webcface.view_base
import webcface.components
import webcface.client_data
import webcface.func
import webcface.temporal_component
from webcface.typing import convertible_to_float
[docs]
class ViewData:
tmp_components: "List[webcface.temporal_component.TemporalComponent]"
components: "Dict[str, webcface.view_base.ViewComponentBase]"
ids: List[str]
def __init__(self) -> None:
self.tmp_components = []
self.components = {}
self.ids = []
[docs]
class ViewComponent(webcface.view_base.ViewComponentBase):
_data: "Optional[webcface.client_data.ClientData]"
_id: str
def __init__(
self,
base: "webcface.view_base.ViewComponentBase",
data: "Optional[webcface.client_data.ClientData]",
id: str,
) -> None:
super().__init__(
type=base._type,
text=base._text,
on_click=base._on_click_func,
text_ref=base._text_ref,
text_color=base._text_color,
bg_color=base._bg_color,
min=base._min,
max=base._max,
step=base._step,
option=base._option,
)
self._data = data
self._id = id
def __eq__(self, other) -> bool:
"""プロパティの比較
:return: id以外のプロパティが全部等しければTrueになる
"""
return isinstance(
other, ViewComponent
) and webcface.view_base.ViewComponentBase.__eq__(self, other)
def __ne__(self, other) -> bool:
return not self == other
@property
def id(self) -> str:
"""要素のid (ver3.0〜)"""
return self._id
@property
def type(self) -> int:
"""コンポーネントの種類
ViewComponentType Enumを使う
"""
return self._type
@property
def text(self) -> str:
"""表示する文字列"""
return self._text
@property
def on_click(self) -> "Optional[webcface.func.Func]":
"""クリックしたときに呼び出す関数"""
if self._on_click_func is not None:
if self._data is None:
raise RuntimeError("internal data not set")
return webcface.func.Func(
webcface.field.Field(
self._data, self._on_click_func._member, self._on_click_func._field
)
)
return None
@property
def on_change(self) -> "Optional[webcface.func.Func]":
"""値が変化したときに呼び出す関数
(ver2.0〜)
run_asyncの引数に変更後の値を入れて呼び出すことで、inputの値を変更する
内部実装はon_clickと共通になっている
"""
return self.on_click
@property
def bind(self) -> "Optional[webcface.text.Variant]":
"""inputの現在の値を取得
(ver2.0〜)
viewを作成したときにbindしたかon_changeをセットしたかに関わらず、
値の変更はbindではなくon_changeから行う
"""
if self._text_ref is not None:
if self._data is None:
raise RuntimeError("internal data not set")
return webcface.text.Variant(
webcface.field.Field(
self._data, self._text_ref._member, self._text_ref._field
)
)
return None
@property
def text_color(self) -> int:
"""文字の色
ViewColor Enumを使う
"""
return self._text_color
@property
def bg_color(self) -> int:
"""背景の色
ViewColor Enumを使う
"""
return self._bg_color
@property
def min(self) -> Optional[float]:
"""inputの最小値
(ver2.0〜)
"""
return self._min
@property
def max(self) -> Optional[float]:
"""inputの最大値
(ver2.0〜)
"""
return self._max
@property
def step(self) -> Optional[float]:
"""inputの刻み幅
(ver2.0〜)
"""
return self._step
@property
def option(self) -> List[Union[float, bool, str]]:
"""inputの選択肢
(ver2.0〜)
"""
return self._option
[docs]
class View(webcface.field.Field):
_vdata: "Optional[ViewData]"
_modified: bool
def __init__(self, base: "webcface.field.Field", field: str = "") -> None:
"""Viewを指すクラス
このコンストラクタを直接使わず、
Member.view(), Member.views(), Member.onViewEntry などを使うこと
詳細は `Viewのドキュメント <https://na-trium-144.github.io/webcface/md_13__view.html>`_ を参照
"""
super().__init__(
base._data, base._member, field if field != "" else base._field
)
self._vdata = None
self._modified = False
@property
def member(self) -> "webcface.member.Member":
"""Memberを返す"""
return webcface.member.Member(self)
@property
def name(self) -> str:
"""field名を返す"""
return self._field
[docs]
def on_change(self, func: Callable) -> Callable:
"""値が変化したときのイベント
(ver2.0〜)
コールバックの引数にはViewオブジェクトが渡される。
まだ値をリクエストされてなければ自動でリクエストされる
"""
self.request()
data = self._data_check()
if self._member not in data.on_view_change:
data.on_view_change[self._member] = {}
data.on_view_change[self._member][self._field] = func
return func
[docs]
def child(self, field: str) -> "View":
"""子フィールドを返す
:return: 「(thisのフィールド名).(子フィールド名)」をフィールド名とするView
"""
return View(self, self._field + "." + field)
[docs]
def request(self) -> None:
"""値の受信をリクエストする"""
req = self._data_check().view_store.add_req(self._member, self._field)
if req > 0:
self._data_check().queue_msg_req(
[webcface.message.ViewReq.new(self._member, self._field, req)]
)
[docs]
def try_get(self) -> Optional[List[ViewComponent]]:
"""ViewをlistまたはNoneで返す、まだリクエストされてなければ自動でリクエストされる"""
self.request()
v = self._data_check().view_store.get_recv(self._member, self._field)
v2: Optional[List[ViewComponent]] = None
if v is not None:
v2 = [ViewComponent(v.components[v_id], self._data, v_id) for v_id in v.ids]
return v2
[docs]
def get(self) -> List[ViewComponent]:
"""Viewをlistで返す、まだリクエストされてなければ自動でリクエストされる"""
v = self.try_get()
return v if v is not None else []
[docs]
def exists(self) -> bool:
"""このフィールドにデータが存在すればtrue
(ver2.0〜)
try_get() などとは違って、実際のデータを受信しない。
リクエストもしない。
"""
return self._field in self._data_check().view_store.get_entry(self._member)
[docs]
def set(
self,
components: List[
Union[
"webcface.temporal_component.TemporalComponent",
str,
bool,
SupportsFloat,
]
],
) -> "View":
"""Viewのリストをセットする
.. deprecated:: ver3.0
"""
for c in components:
self.add(c)
return self
def __enter__(self) -> "View":
"""with構文の最初で自動でinit()を呼ぶ"""
self.init()
return self
[docs]
def init(self) -> "View":
"""このViewオブジェクトにaddした内容を初期化する"""
self._vdata = ViewData()
self._modified = True
return self
def __exit__(self, type, value, tb) -> None:
"""with構文の終わりに自動でsync()を呼ぶ"""
self.sync()
[docs]
def sync(self) -> "View":
"""Viewの内容をclientに反映し送信可能にする"""
self._set_check()
if self._modified and self._vdata is not None:
data = self._set_check()
self._vdata.components = {}
self._vdata.ids = []
data_idx: Dict[int, int] = {}
for c in self._vdata.tmp_components:
idx = data_idx.get(c._view_type, 0)
data_idx[c._view_type] = idx + 1
c.lock_tmp(data, "v", self._field, f"..{c._view_type}.{idx}")
self._vdata.components[c.id] = c.to_view()
self._vdata.ids.append(c.id)
data.view_store.set_send(self._field, self._vdata)
on_change = data.on_view_change.get(self._member, {}).get(self._field)
if on_change is not None:
on_change(self)
self._modified = False
return self
[docs]
def add(
self,
*args: Union[
"webcface.temporal_component.TemporalComponent", str, bool, SupportsFloat
],
**kwargs,
) -> "View":
"""Viewに要素を追加
:arg args: 追加する要素
複数指定した場合すべて追加される。
:arg kwargs: (ver3.0〜) argsが初期化済みの要素でない場合、要素の初期化時に渡すオプション。
詳細は TemporalComponent のコンストラクタを参照
"""
if self._vdata is None:
self.init()
assert self._vdata is not None
assert len(args) > 0, "no components given to View.add()"
for c in args:
if isinstance(c, webcface.temporal_component.TemporalComponent):
if len(kwargs) > 0:
raise ValueError(
f"kwargs is not allowed because {c} is already a component"
)
self._vdata.tmp_components.append(c)
elif isinstance(c, str):
while "\n" in c:
s = c[: c.find("\n")]
self._vdata.tmp_components.append(
webcface.components.text(s, **kwargs)
)
self._vdata.tmp_components.append(
webcface.components.new_line(**kwargs)
)
c = c[c.find("\n") + 1 :]
if c != "":
self._vdata.tmp_components.append(
webcface.components.text(c, **kwargs)
)
else:
self._vdata.tmp_components.append(
webcface.components.text(str(c), **kwargs)
)
self._modified = True
return self