From d6c4b527638b3e6bf8f6a385efaf00e5abd5ef34 Mon Sep 17 00:00:00 2001 From: kosay Date: Sat, 27 May 2023 16:31:00 +0900 Subject: [PATCH] refactor: remove unused features and code --- Cargo.toml | 13 - examples/new_text.rs | 312 ----------------- examples/stack_view.rs | 100 ------ examples/widget_config.rs | 64 ---- src/error.rs | 16 - src/event/kubernetes/log.rs | 20 -- src/event/kubernetes/pod.rs | 65 ---- src/tui_wrapper/widget/complex.rs | 6 - src/tui_wrapper/widget/complex/stack.rs | 442 ------------------------ 9 files changed, 1038 deletions(-) delete mode 100644 examples/new_text.rs delete mode 100644 examples/stack_view.rs delete mode 100644 examples/widget_config.rs delete mode 100644 src/tui_wrapper/widget/complex/stack.rs diff --git a/Cargo.toml b/Cargo.toml index f81ddc9d..24dc177b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,6 @@ keywords = ["kubernetes", "tui", "terminal", "monitor"] [features] default = [] -mock = [] -mock-failed = [] -new-log-stream = [] -stack-widget = [] -new-text = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -71,11 +66,3 @@ mockall = "0.11.4" mockall_double = "0.3.0" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } rstest = "0.17.0" - -[[example]] -name = "stack_view" -required-features = ["stack-widget"] - -[[example]] -name = "new_text" - diff --git a/examples/new_text.rs b/examples/new_text.rs deleted file mode 100644 index f8306969..00000000 --- a/examples/new_text.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::{io::stdout, time::Duration}; - -use crossterm::{ - event::{poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; - -use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; - -use kubetui::{ - signal::signal_handler, - tui_wrapper::{ - event::EventResult, - widget::{RenderTrait, Text, WidgetTrait}, - Window, WindowEvent, - }, -}; - -// {{{ sample string -const DATA: &str = r#"あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお -012345あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお -あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお012345 -apiVersion: v1 -kind: Pod -metadata: - annotations: - kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.65.4:6443 - kubernetes.io/config.hash: f76ea91a200a6b1cfe31c7a114460aac - kubernetes.io/config.mirror: f76ea91a200a6b1cfe31c7a114460aac - kubernetes.io/config.seen: "2022-05-15T00:52:21.390862747Z" - kubernetes.io/config.source: file - seccomp.security.alpha.kubernetes.io/pod: runtime/default - creationTimestamp: "2022-05-15T00:52:26Z" - labels: - component: kube-apiserver - tier: control-plane - name: kube-apiserver-docker-desktop - namespace: kube-system - ownerReferences: - - apiVersion: v1 - controller: true - kind: Node - name: docker-desktop - uid: 1479614d-162f-44a6-9d9b-b56eaab73a6b - resourceVersion: "870849" - uid: 8328dc22-45e8-4061-81ac-bfc92576b9c6 -spec: - containers: - - command: - - kube-apiserver - - --advertise-address=192.168.65.4 - - --allow-privileged=true - - --authorization-mode=Node,RBAC - - --client-ca-file=/run/config/pki/ca.crt - - --enable-admission-plugins=NodeRestriction - - --enable-bootstrap-token-auth=true - - --etcd-cafile=/run/config/pki/etcd/ca.crt - - --etcd-certfile=/run/config/pki/apiserver-etcd-client.crt - - --etcd-keyfile=/run/config/pki/apiserver-etcd-client.key - - --etcd-servers=https://127.0.0.1:2379 - - --kubelet-client-certificate=/run/config/pki/apiserver-kubelet-client.crt - - --kubelet-client-key=/run/config/pki/apiserver-kubelet-client.key - - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - - --proxy-client-cert-file=/run/config/pki/front-proxy-client.crt - - --proxy-client-key-file=/run/config/pki/front-proxy-client.key - - --requestheader-allowed-names=front-proxy-client - - --requestheader-client-ca-file=/run/config/pki/front-proxy-ca.crt - - --requestheader-extra-headers-prefix=X-Remote-Extra- - - --requestheader-group-headers=X-Remote-Group - - --requestheader-username-headers=X-Remote-User - - --secure-port=6443 - - --service-account-issuer=https://kubernetes.default.svc.cluster.local - - --service-account-key-file=/run/config/pki/sa.pub - - --service-account-signing-key-file=/run/config/pki/sa.key - - --service-cluster-ip-range=10.96.0.0/12 - - --tls-cert-file=/run/config/pki/apiserver.crt - - --tls-private-key-file=/run/config/pki/apiserver.key - - --watch-cache=false - image: k8s.gcr.io/kube-apiserver:v1.24.0 - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 8 - httpGet: - host: 192.168.65.4 - path: /livez - port: 6443 - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - name: kube-apiserver - readinessProbe: - failureThreshold: 3 - httpGet: - host: 192.168.65.4 - path: /readyz - port: 6443 - scheme: HTTPS - periodSeconds: 1 - successThreshold: 1 - timeoutSeconds: 15 - resources: - requests: - cpu: 250m - startupProbe: - failureThreshold: 24 - httpGet: - host: 192.168.65.4 - path: /livez - port: 6443 - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /etc/ssl/certs - name: ca-certs - readOnly: true - - mountPath: /etc/ca-certificates - name: etc-ca-certificates - readOnly: true - - mountPath: /run/config/pki - name: k8s-certs - readOnly: true - - mountPath: /usr/local/share/ca-certificates - name: usr-local-share-ca-certificates - readOnly: true - - mountPath: /usr/share/ca-certificates - name: usr-share-ca-certificates - readOnly: true - dnsPolicy: ClusterFirst - enableServiceLinks: true - hostNetwork: true - nodeName: docker-desktop - preemptionPolicy: PreemptLowerPriority - priority: 2000001000 - priorityClassName: system-node-critical - restartPolicy: Always - schedulerName: default-scheduler - securityContext: - seccompProfile: - type: RuntimeDefault - terminationGracePeriodSeconds: 30 - tolerations: - - effect: NoExecute - operator: Exists - volumes: - - hostPath: - path: /etc/ssl/certs - type: DirectoryOrCreate - name: ca-certs - - hostPath: - path: /etc/ca-certificates - type: DirectoryOrCreate - name: etc-ca-certificates - - hostPath: - path: /run/config/pki - type: DirectoryOrCreate - name: k8s-certs - - hostPath: - path: /usr/local/share/ca-certificates - type: DirectoryOrCreate - name: usr-local-share-ca-certificates - - hostPath: - path: /usr/share/ca-certificates - type: DirectoryOrCreate - name: usr-share-ca-certificates -status: - conditions: - - lastProbeTime: null - lastTransitionTime: "2022-06-04T14:25:17Z" - status: "True" - type: Initialized - - lastProbeTime: null - lastTransitionTime: "2022-06-04T14:25:36Z" - status: "True" - type: Ready - - lastProbeTime: null - lastTransitionTime: "2022-06-04T14:25:36Z" - status: "True" - type: ContainersReady - - lastProbeTime: null - lastTransitionTime: "2022-06-04T14:25:17Z" - status: "True" - type: PodScheduled - containerStatuses: - - containerID: docker://5c50df15f0756d7e7ee87dc60888b3519dd2d75af08fc2319f49f26e878a4a7a - image: k8s.gcr.io/kube-apiserver:v1.24.0 - imageID: docker://sha256:529072250ccc6301cb341cd7359c9641b94a6f837f86f82b1223a59bb0712e64 - lastState: - terminated: - containerID: docker://277ce0397db0efa177407710c413cb702bd0dca456009dc999c775cd5ad659f6 - exitCode: 255 - finishedAt: "2022-06-04T14:25:09Z" - reason: Error - startedAt: "2022-06-04T10:24:31Z" - name: kube-apiserver - ready: true - restartCount: 15 - started: true - state: - running: - startedAt: "2022-06-04T14:25:18Z" - hostIP: 192.168.65.4 - phase: Running - podIP: 192.168.65.4 - podIPs: - - ip: 192.168.65.4 - qosClass: Burstable - startTime: "2022-06-04T14:25:17Z" -"#; -// }}} - -use std::io::Write; -fn main() { - let default_hook = std::panic::take_hook(); - - std::panic::set_hook(Box::new(move |info| { - disable_raw_mode().unwrap(); - execute!(std::io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap(); - - std::io::stderr() - .write_all(b"\x1b[31mPanic! disable raw mode\x1b[39m") - .unwrap(); - - default_hook(info); - })); - - signal_handler(); - - enable_raw_mode().unwrap(); - - execute!(stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap(); - - let backend = CrosstermBackend::new(stdout()); - let mut terminal = Terminal::new(backend).unwrap(); - - terminal.clear().unwrap(); - - let item = DATA.lines().map(|l| l.into()).collect::>(); - - let mut text = Text::builder() - .items(item) - .wrap() - .action(KeyCode::Char('q'), |_| { - EventResult::Window(WindowEvent::CloseWindow) - }) - .action(KeyCode::Esc, |_| { - EventResult::Window(WindowEvent::CloseWindow) - }) - .build(); - - text.update_chunk(terminal.size().unwrap()); - - loop { - terminal - .draw(|f| { - text.render(f, true); - }) - .unwrap(); - - if poll(Duration::from_millis(200)).unwrap() { - let result = { - match read() { - Ok(ev) => match ev { - Event::Key(key) => match text.on_key_event(key) { - EventResult::Window(w) => w, - EventResult::Callback(Some(cb)) => { - if let EventResult::Window(ev) = (cb)(&mut Window::default()) { - ev - } else { - WindowEvent::Continue - } - } - _ => WindowEvent::Continue, - }, - - Event::Mouse(ev) => { - text.on_mouse_event(ev); - - WindowEvent::Continue - } - Event::Resize(w, h) => { - text.update_chunk(Rect::new(0, 0, w, h)); - - WindowEvent::Continue - } - _ => WindowEvent::Continue, - }, - Err(_) => break, - } - }; - - if matches!(result, WindowEvent::CloseWindow) { - break; - } - } - } - - disable_raw_mode().unwrap(); - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - ) - .unwrap(); -} diff --git a/examples/stack_view.rs b/examples/stack_view.rs deleted file mode 100644 index 84c528cb..00000000 --- a/examples/stack_view.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::io::stdout; - -use crossterm::{ - event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; - -use tui::{ - backend::CrosstermBackend, - layout::{Constraint, Direction, Layout, Rect}, - widgets::Widget, - Terminal, -}; - -#[cfg(feature = "stack-widget")] -use kubetui::tui_wrapper::{ - util::child_window_chunk, - widget::{config::WidgetConfig, List, ListBuilder, RenderTrait, Stack, WidgetTrait}, -}; - -fn main() { - enable_raw_mode().unwrap(); - - execute!(stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap(); - - let backend = CrosstermBackend::new(stdout()); - let mut terminal = Terminal::new(backend).unwrap(); - - terminal.clear().unwrap(); - - let chunk = child_window_chunk(50, 50, terminal.size().unwrap()); - - let mut stack = Stack::builder() - .widget_config(&WidgetConfig::builder().title("Stack").build()) - .build(); - - stack.push_widget( - List::builder() - .widget_config(&WidgetConfig::builder().title("List-0").build()) - .build(), - ); - stack.push_widget( - List::builder() - .widget_config(&WidgetConfig::builder().title("List-1").build()) - .build(), - ); - stack.push_widget( - List::builder() - .widget_config(&WidgetConfig::builder().title("List-2").build()) - .build(), - ); - stack.push_widget( - List::builder() - .widget_config(&WidgetConfig::builder().title("List-3").build()) - .build(), - ); - stack.push_widget( - List::builder() - .widget_config(&WidgetConfig::builder().title("List-4").build()) - .build(), - ); - - stack.update_chunk(chunk); - - // dbg!(stack); - - loop { - terminal - .draw(|f| { - let chunk = child_window_chunk(50, 50, f.size()); - stack.update_chunk(chunk); - stack.render(f, true); - }) - .unwrap(); - - match read() { - Ok(ev) => match ev { - Event::Key(key) => match key.code { - KeyCode::Char('q') => break, - KeyCode::Char('j') => stack.next_widget(), - KeyCode::Char('r') => stack.clear(), - _ => {} - }, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - _ => {} - }, - Err(_) => break, - } - } - - disable_raw_mode().unwrap(); - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - ) - .unwrap(); -} diff --git a/examples/widget_config.rs b/examples/widget_config.rs deleted file mode 100644 index 680efce1..00000000 --- a/examples/widget_config.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::io::stdout; - -use crossterm::{ - event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; - -use tui::{backend::CrosstermBackend, Terminal}; - -use kubetui::tui_wrapper::{util::child_window_chunk, widget::config::WidgetConfig}; - -fn main() { - enable_raw_mode().unwrap(); - - execute!(stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap(); - - let backend = CrosstermBackend::new(stdout()); - let mut terminal = Terminal::new(backend).unwrap(); - - terminal.clear().unwrap(); - - let widget_config = WidgetConfig::builder() - .title("Title") - // .disable_focus() - .build(); - - // dbg!(stack); - - let mut focus = true; - loop { - terminal - .draw(|f| { - let chunk = child_window_chunk(50, 50, f.size()); - - let block = widget_config.render_block(focus); - - f.render_widget(block, chunk); - }) - .unwrap(); - - match read() { - Ok(ev) => match ev { - Event::Key(key) => match key.code { - KeyCode::Char('q') => break, - KeyCode::Char('j') => focus = !focus, - _ => {} - }, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - _ => {} - }, - Err(_) => break, - } - } - - disable_raw_mode().unwrap(); - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - ) - .unwrap(); -} diff --git a/src/error.rs b/src/error.rs index ad941e14..442930ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,22 +4,6 @@ pub use anyhow::{anyhow, Result}; use thiserror::Error as TError; -#[cfg(any(feature = "mock", feature = "mock-failed"))] -#[derive(Debug, TError)] -pub enum Error { - #[error("MockError: {0}")] - Mock(&'static str), - #[error("kubeError: {0}")] - Kube(#[from] kube::Error), - #[error("{0}")] - Raw(String), - #[error("{0:?}")] - VecRaw(Vec), - #[error(transparent)] - IO(#[from] std::io::Error), -} - -#[cfg(not(any(feature = "mock", feature = "mock-failed")))] #[derive(Debug, TError)] pub enum Error { #[error(transparent)] diff --git a/src/event/kubernetes/log.rs b/src/event/kubernetes/log.rs index 38cf56a8..127dcb03 100644 --- a/src/event/kubernetes/log.rs +++ b/src/event/kubernetes/log.rs @@ -850,24 +850,4 @@ impl Worker for FetchLogStream { Ok(()) } - - #[cfg(feature = "mock")] - async fn run(&self) -> Self::Output { - async { - let stream = vec!["line 0", "line 1", "line 2", "line 3", "line 4"]; - - for s in stream { - let mut buf = self.buf.write().await; - buf.push(s.to_string()); - } - } - .await; - - Err(anyhow!(Error::Mock("follow_container_log_stream failed"))) - } - - #[cfg(feature = "mock-failed")] - async fn run(&self) -> Self::Output { - Err(anyhow!(Error::Mock("follow_container_log_stream failed"))) - } } diff --git a/src/event/kubernetes/pod.rs b/src/event/kubernetes/pod.rs index 6bcbca50..1907e8ab 100644 --- a/src/event/kubernetes/pod.rs +++ b/src/event/kubernetes/pod.rs @@ -77,7 +77,6 @@ impl Worker for PodPollWorker { } } -#[cfg(not(any(feature = "mock", feature = "mock-failed")))] async fn get_pods_per_namespace( client: &KubeClient, namespaces: &[String], @@ -123,70 +122,6 @@ async fn get_pods_per_namespace( .await } -#[cfg(feature = "mock")] -async fn get_pods_per_namespace( - _: &Client, - _: &str, - namespaces: &[String], -) -> Result>>> { - if insert_ns(namespaces) { - let ret = namespaces - .iter() - .enumerate() - .map(|(i, ns)| { - vec![ - vec![ - ns.to_string(), - "test-0".to_string(), - "1/1".to_string(), - "Running".to_string(), - "10d".to_string(), - ], - vec![ - ns.to_string(), - "test-1".to_string(), - "2/2".to_string(), - "Running".to_string(), - "10d".to_string(), - ], - ] - }) - .collect(); - Ok(ret) - } else { - Ok(vec![vec![ - vec![ - "mock-test-0".to_string(), - "1/1".to_string(), - "Running".to_string(), - "10d".to_string(), - ], - vec![ - "mock-test-1".to_string(), - "1/1".to_string(), - "Running".to_string(), - "11d".to_string(), - ], - vec![ - "mock-test-2".to_string(), - "1/1".to_string(), - "Running".to_string(), - "13d".to_string(), - ], - ]]) - } -} - -#[cfg(feature = "mock-failed")] -async fn get_pods_per_namespace( - _: &Client, - _: &str, - _: &[String], -) -> Result>>> { - use crate::error::{anyhow, Error}; - Err(anyhow!(Error::Mock("Mock get_pods_per_namespace failed"))) -} - async fn get_pod_info(client: &KubeClient, namespaces: &[String]) -> Result { let jobs = get_pods_per_namespace(client, namespaces).await; diff --git a/src/tui_wrapper/widget/complex.rs b/src/tui_wrapper/widget/complex.rs index 97fd7fa0..2fc45952 100644 --- a/src/tui_wrapper/widget/complex.rs +++ b/src/tui_wrapper/widget/complex.rs @@ -2,12 +2,6 @@ mod input; mod multiple_select; mod single_select; -#[cfg(feature = "stack-widget")] -mod stack; - pub use input::InputForm; pub use multiple_select::{MultipleSelect, MultipleSelectBuilder}; pub use single_select::{SingleSelect, SingleSelectBuilder}; - -#[cfg(feature = "stack-widget")] -pub use stack::{Stack, StackBuilder}; diff --git a/src/tui_wrapper/widget/complex/stack.rs b/src/tui_wrapper/widget/complex/stack.rs deleted file mode 100644 index 7d7b228d..00000000 --- a/src/tui_wrapper/widget/complex/stack.rs +++ /dev/null @@ -1,442 +0,0 @@ -use std::{cmp::Ordering, collections::HashMap, rc::Rc}; - -use crossterm::event::{KeyEvent, MouseEvent}; -use event::UserEvent; - -use crate::{ - event::{EventResult, InnerCallback}, - widget::{config::WidgetConfig, Item, RenderTrait, Widget, WidgetTrait}, - Window, -}; - -use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame}; - -use derivative::*; - -#[derive(Derivative)] -#[derivative(Debug, Default)] -pub struct Stack<'a> { - id: String, - widget_config: WidgetConfig, - chunk: Rect, - widgets: Vec>, - current_index: usize, - #[derivative(Debug = "ignore")] - callbacks: HashMap, -} - -impl<'a> Stack<'a> { - pub fn builder() -> StackBuilder<'static> { - StackBuilder::default() - } - - pub fn push_widget(&mut self, w: impl Into>) { - self.widgets.push(w.into()); - } - - pub fn current_widget(&self) -> &Widget<'a> { - &self.widgets[self.current_index] - } - - pub fn current_widget_mut(&mut self) -> &mut Widget<'a> { - &mut self.widgets[self.current_index] - } - - pub fn current_widget_chunk(&self) -> Rect { - self.widgets[self.current_index].chunk() - } - - pub fn next_widget(&mut self) { - self.current_index += 1; - } -} - -#[derive(Derivative)] -#[derivative(Debug, Default)] -pub struct StackBuilder<'a> { - id: String, - widget_config: WidgetConfig, - widgets: Vec>, - #[derivative(Debug = "ignore")] - callbacks: HashMap, -} - -impl<'a> StackBuilder<'a> { - pub fn id(mut self, id: impl Into) -> Self { - self.id = id.into(); - self - } - - pub fn widget_config(mut self, widget_config: &WidgetConfig) -> Self { - self.widget_config = widget_config.clone(); - self - } - - pub fn widgets(mut self, widgets: impl Into>>) -> Self { - self.widgets = widgets.into(); - self - } - - pub fn widget(mut self, widget: Widget<'a>) -> Self { - self.widgets.push(widget); - self - } - - pub fn action(mut self, ev: E, cb: F) -> Self - where - E: Into, - F: Fn(&mut Window) -> EventResult + 'static, - { - self.callbacks.insert(ev.into(), Rc::new(cb)); - self - } - - pub fn build(self) -> Stack<'a> { - Stack { - id: self.id, - widget_config: self.widget_config, - chunk: Default::default(), - widgets: self.widgets, - current_index: 0, - callbacks: self.callbacks, - } - } -} - -impl WidgetTrait for Stack<'_> { - fn id(&self) -> &str { - &self.id - } - - fn widget_config(&self) -> &WidgetConfig { - &self.widget_config - } - - fn widget_config_mut(&mut self) -> &mut WidgetConfig { - &mut self.widget_config - } - - fn focusable(&self) -> bool { - true - } - - fn widget_item(&self) -> Option { - self.current_widget().widget_item() - } - - fn chunk(&self) -> Rect { - self.chunk - } - - fn select_index(&mut self, i: usize) { - self.current_widget_mut().select_index(i) - } - - fn select_next(&mut self, i: usize) { - self.current_widget_mut().select_next(i) - } - - fn select_prev(&mut self, i: usize) { - self.current_widget_mut().select_prev(i) - } - - fn select_first(&mut self) { - self.current_widget_mut().select_first() - } - - fn select_last(&mut self) { - self.current_widget_mut().select_last() - } - - fn append_widget_item(&mut self, item: Item) { - self.current_widget_mut().append_widget_item(item) - } - - fn update_widget_item(&mut self, item: Item) { - self.current_widget_mut().update_widget_item(item) - } - - fn on_mouse_event(&mut self, ev: MouseEvent) -> EventResult { - todo!() - } - - fn on_key_event(&mut self, ev: KeyEvent) -> EventResult { - // TODO 選択されたらindexをインクリメントしてwidgetをとじていく - // TODO 洗濯されたアイテムをためていく - // TODO 最後にコールバックを返す - - todo!() - } - - fn update_chunk(&mut self, chunk: Rect) { - let center_index = if self.widgets.len() % 2 == 0 { - (self.widgets.len() / 2).saturating_sub(1) - } else { - self.widgets.len() / 2 - }; - - for (i, w) in self.widgets.iter_mut().enumerate() { - let delta = (center_index as i16 - i as i16).unsigned_abs(); - - let offset_x: u16 = 2 * delta; - let offset_y: u16 = delta; - - let ch = match i.cmp(¢er_index) { - Ordering::Less => Rect::new( - chunk.x.saturating_sub(offset_x), - chunk.y + offset_y, - chunk.width, - chunk.height, - ), - Ordering::Equal => chunk, - Ordering::Greater => Rect::new( - chunk.x + offset_x, - chunk.y.saturating_sub(offset_y), - chunk.width, - chunk.height, - ), - }; - - w.update_chunk(ch); - } - } - - fn clear(&mut self) { - self.widgets.iter_mut().for_each(|w| w.clear()); - self.current_index = 0; - - *(self.widget_config.append_title_mut()) = None; - } -} - -impl RenderTrait for Stack<'_> { - fn render(&mut self, f: &mut Frame<'_, B>, _selected: bool) - where - B: Backend, - { - if self.widgets.is_empty() { - return; - } - - for i in (self.current_index..self.widgets.len()).rev() { - f.render_widget(Clear, self.widgets[i].chunk()); - self.widgets[i].render(f, self.current_index == i); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod update_chunk { - use crate::widget::Text; - use pretty_assertions::assert_eq; - - use super::*; - - #[test] - fn chunk_position_one_widget() { - // " ┌Title─┐ ", - // " │ │ ", - // " │ │ ", - // " │ │ ", - // " │ │ ", - // " │ │ ", - // " │ │ ", - // " └──────┘ ", - let base_chunk = Rect::new(50, 50, 50, 50); - - let mut w = Stack::default(); - - w.push_widget(Widget::Text(Text::default())); - - w.update_chunk(base_chunk); - - let expected = Rect::new(50, 50, 50, 50); - - assert_eq!(expected, w.widgets[0].chunk()); - } - - #[test] - fn chunk_position_even_widgets() { - // " ┌─ T3 ─┐ ", - // " ┌─ T2 ─┐ │ ", - // " ┌─ T1 ─┐ │ │ ", T1 = center - // " ┌─ T0 ─┐ │ │ │ ", - // " │ │ │ │ │ ", - // " │ │ │ │ │ ", - // " │ │ │ │ │ ", - // " │ │ │ │─┘ ", - // " │ │ │─┘ ", - // " │ │─┘ ", - // " └──────┘ ", - - let base_chunk = Rect::new(50, 50, 50, 50); - - let mut w = Stack::default(); - - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - - w.update_chunk(base_chunk); - - assert_eq!(Rect::new(56, 47, 50, 50), w.widgets[5].chunk()); - assert_eq!(Rect::new(54, 48, 50, 50), w.widgets[4].chunk()); - assert_eq!(Rect::new(52, 49, 50, 50), w.widgets[3].chunk()); - assert_eq!(Rect::new(50, 50, 50, 50), w.widgets[2].chunk()); // center - assert_eq!(Rect::new(48, 51, 50, 50), w.widgets[1].chunk()); - assert_eq!(Rect::new(46, 52, 50, 50), w.widgets[0].chunk()); - } - - #[test] - fn chunk_position_odd_widgets() { - // " ┌─ T2 ─┐ ", - // " ┌─ T1 ─┐ │ ", T1 = center - // " ┌─ T0 ─┐ │ │ ", - // " │ │ │ │ ", - // " │ │ │ │ ", - // " │ │ │ │ ", - // " │ │ │ │ ", - // " │ │ │─┘ ", - // " │ │─┘ ", - // " └──────┘ ", - - let base_chunk = Rect::new(50, 50, 50, 50); - - let mut w = Stack::default(); - - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - w.push_widget(Widget::Text(Text::default())); - - w.update_chunk(base_chunk); - - assert_eq!(Rect::new(54, 48, 50, 50), w.widgets[4].chunk()); - assert_eq!(Rect::new(52, 49, 50, 50), w.widgets[3].chunk()); - assert_eq!(Rect::new(50, 50, 50, 50), w.widgets[2].chunk()); // center - assert_eq!(Rect::new(48, 51, 50, 50), w.widgets[1].chunk()); - assert_eq!(Rect::new(46, 52, 50, 50), w.widgets[0].chunk()); - } - } - mod e2e { - use super::*; - - use crate::{ - util::child_window_chunk, - widget::{List, RenderTrait, WidgetTrait}, - }; - use tui::{backend::TestBackend, buffer::Buffer, layout::Rect, Terminal}; - - fn setup_list_widgets(n: usize) -> Vec> { - (0..n) - .map(|i| { - List::builder() - .widget_config( - &WidgetConfig::builder() - .title(format!("List-{}", i)) - .disable_focus() - .build(), - ) - .build() - .into() - }) - .collect() - } - - #[test] - fn chunk_position_odd_widgets() { - let backend = TestBackend::new(46, 20); - let mut terminal = Terminal::new(backend).unwrap(); - - let terminal_chunk = Rect::new(0, 0, 46, 20); - - let base_chunk = child_window_chunk(50, 50, terminal_chunk); - - let mut w = Stack::builder().widgets(setup_list_widgets(5)).build(); - - w.update_chunk(base_chunk); - - terminal - .draw(|f| { - w.render(f, true); - }) - .unwrap(); - - let expected = Buffer::with_lines::<&'static str>(vec![ - " ", - " ", - " ", - " ┌─── List-4 ──────────┐ ", - " ┌─── List-3 ──────────┐ │ ", - " ┌─── List-2 ──────────┐ │ │ ", - " ┌─── List-1 ──────────┐ │ │ │ ", - " ┌─── List-0 ──────────┐ │ │ │ │ ", - " │ │ │ │ │ │ ", - " │ │ │ │ │ │ ", - " │ │ │ │ │ │ ", - " │ │ │ │ │ │ ", - " │ │ │ │ │─┘ ", - " │ │ │ │─┘ ", - " │ │ │─┘ ", - " │ │─┘ ", - " └─────────────────────┘ ", - " ", - " ", - " ", - ]); - - terminal.backend().assert_buffer(&expected) - } - - #[test] - fn chunk_position_even_widgets() { - let backend = TestBackend::new(46, 20); - let mut terminal = Terminal::new(backend).unwrap(); - - let terminal_chunk = Rect::new(0, 0, 46, 20); - - let base_chunk = child_window_chunk(50, 50, terminal_chunk); - - let mut w = Stack::builder().widgets(setup_list_widgets(4)).build(); - w.update_chunk(base_chunk); - - terminal - .draw(|f| { - w.render(f, true); - }) - .unwrap(); - - let expected = Buffer::with_lines::<&'static str>(vec![ - " ", - " ", - " ", - " ┌─── List-3 ──────────┐ ", - " ┌─── List-2 ──────────┐ │ ", - " ┌─── List-1 ──────────┐ │ │ ", - " ┌─── List-0 ──────────┐ │ │ │ ", - " │ │ │ │ │ ", - " │ │ │ │ │ ", - " │ │ │ │ │ ", - " │ │ │ │ │ ", - " │ │ │ │ │ ", - " │ │ │ │─┘ ", - " │ │ │─┘ ", - " │ │─┘ ", - " └─────────────────────┘ ", - " ", - " ", - " ", - " ", - ]); - - terminal.backend().assert_buffer(&expected) - } - } -}