termux_gui/view.rs
1//! Base View type and common view operations
2//!
3//! ## Important Notes on Dimensions and Units
4//!
5//! Android uses two different units for View dimensions:
6//! - **dp (density-independent pixels)**: Default unit, scales with screen density
7//! - **px (pixels)**: Actual screen pixels
8//!
9//! ### Critical for TabLayout/ScrollView
10//!
11//! When working with `HorizontalScrollView` or `TabLayout`:
12//! 1. `get_dimensions()` returns values in **pixels (px)**
13//! 2. `set_width()` and `set_height()` use **dp** by default
14//! 3. **You must use `set_width_px()` and `set_height_px()`** to match dimensions!
15//!
16//! ### Example
17//!
18//! ```rust,no_run
19//! # use termux_gui::{Activity, Result};
20//! # fn example(activity: &mut Activity, scroll_view_id: i64, page_id: i64) -> Result<()> {
21//! // Get dimensions (returns pixels)
22//! let (width_px, height_px) = scroll_view.view().get_dimensions(activity)?;
23//!
24//! // WRONG: This uses dp, not px!
25//! // page.view().set_width(activity, width_px)?; // Width mismatch!
26//!
27//! // CORRECT: Use set_width_px to match pixel values
28//! page.view().set_width_px(activity, width_px)?; // Perfect match!
29//! # Ok(())
30//! # }
31//! ```
32
33use serde_json::json;
34use crate::activity::Activity;
35use crate::error::Result;
36
37/// Special dimension constants for Android layouts
38pub const MATCH_PARENT: i32 = -1;
39pub const WRAP_CONTENT: i32 = -2;
40
41/// Base View structure
42pub struct View {
43 id: i64,
44}
45
46impl View {
47 /// Create a new View with the given ID
48 pub fn new(id: i64) -> Self {
49 View { id }
50 }
51
52 /// Get the view ID
53 pub fn id(&self) -> i64 {
54 self.id
55 }
56
57 /// Set view width in dp (density-independent pixels)
58 ///
59 /// **Note**: If you need to match dimensions from `get_dimensions()`,
60 /// use `set_width_px()` instead, as `get_dimensions()` returns pixels.
61 pub fn set_width(&self, activity: &mut Activity, width: i32) -> Result<()> {
62 activity.send(&json!({
63 "method": "setWidth",
64 "params": {
65 "aid": activity.id(),
66 "id": self.id,
67 "width": width
68 }
69 }))?;
70 Ok(())
71 }
72
73 /// Set view width in pixels
74 ///
75 /// **Use this when matching dimensions from `get_dimensions()`**
76 /// which returns pixel values, not dp values.
77 pub fn set_width_px(&self, activity: &mut Activity, width: i32) -> Result<()> {
78 activity.send(&json!({
79 "method": "setWidth",
80 "params": {
81 "aid": activity.id(),
82 "id": self.id,
83 "width": width,
84 "px": true
85 }
86 }))?;
87 Ok(())
88 }
89
90 /// Set view height in dp (density-independent pixels)
91 ///
92 /// **Note**: If you need to match dimensions from `get_dimensions()`,
93 /// use `set_height_px()` instead, as `get_dimensions()` returns pixels.
94 pub fn set_height(&self, activity: &mut Activity, height: i32) -> Result<()> {
95 activity.send(&json!({
96 "method": "setHeight",
97 "params": {
98 "aid": activity.id(),
99 "id": self.id,
100 "height": height
101 }
102 }))?;
103 Ok(())
104 }
105
106 /// Set view height in pixels
107 ///
108 /// **Use this when matching dimensions from `get_dimensions()`**
109 /// which returns pixel values, not dp values.
110 pub fn set_height_px(&self, activity: &mut Activity, height: i32) -> Result<()> {
111 activity.send(&json!({
112 "method": "setHeight",
113 "params": {
114 "aid": activity.id(),
115 "id": self.id,
116 "height": height,
117 "px": true
118 }
119 }))?;
120 Ok(())
121 }
122
123 /// Set view width and height
124 pub fn set_dimensions(&self, activity: &mut Activity, width: i32, height: i32) -> Result<()> {
125 // Call both methods since there's no combined setDimensions
126 self.set_width(activity, width)?;
127 self.set_height(activity, height)?;
128 Ok(())
129 }
130
131 /// Set view margin
132 pub fn set_margin(&self, activity: &mut Activity, margin: i32) -> Result<()> {
133 activity.send(&json!({
134 "method": "setMargin",
135 "params": {
136 "aid": activity.id(),
137 "id": self.id,
138 "margin": margin
139 }
140 }))?;
141 Ok(())
142 }
143
144 /// Set view width to WRAP_CONTENT
145 pub fn set_width_wrap_content(&self, activity: &mut Activity) -> Result<()> {
146 self.set_width(activity, WRAP_CONTENT)
147 }
148
149 /// Set view height to WRAP_CONTENT
150 pub fn set_height_wrap_content(&self, activity: &mut Activity) -> Result<()> {
151 self.set_height(activity, WRAP_CONTENT)
152 }
153
154 /// Set view width to MATCH_PARENT
155 pub fn set_width_match_parent(&self, activity: &mut Activity) -> Result<()> {
156 self.set_width(activity, MATCH_PARENT)
157 }
158
159 /// Set view height to MATCH_PARENT
160 pub fn set_height_match_parent(&self, activity: &mut Activity) -> Result<()> {
161 self.set_height(activity, MATCH_PARENT)
162 }
163
164 /// Set LinearLayout parameters for this view
165 ///
166 /// # Arguments
167 /// * `activity` - The activity
168 /// * `weight` - Layout weight (higher weight = more space)
169 /// * `position` - Optional position index in the layout
170 pub fn set_linear_layout_params(&self, activity: &mut Activity, weight: i32, position: Option<i32>) -> Result<()> {
171 let mut params = json!({
172 "aid": activity.id(),
173 "id": self.id,
174 "weight": weight
175 });
176
177 if let Some(pos) = position {
178 params["position"] = json!(pos);
179 }
180
181 activity.send(&json!({
182 "method": "setLinearLayoutParams",
183 "params": params
184 }))?;
185 Ok(())
186 }
187
188 /// Set GridLayout parameters for this view
189 ///
190 /// # Arguments
191 /// * `row` - Row index (0-based)
192 /// * `col` - Column index (0-based)
193 /// * `row_size` - Number of rows this view spans (default 1)
194 /// * `col_size` - Number of columns this view spans (default 1)
195 /// * `alignment_row` - Vertical alignment: "top", "bottom", "center", "baseline", "fill"
196 /// * `alignment_col` - Horizontal alignment: "left", "right", "center", "fill"
197 pub fn set_grid_layout_params(&self, activity: &mut Activity,
198 row: i32, col: i32,
199 row_size: i32, col_size: i32,
200 alignment_row: &str, alignment_col: &str) -> Result<()> {
201 activity.send(&json!({
202 "method": "setGridLayoutParams",
203 "params": {
204 "aid": activity.id(),
205 "id": self.id,
206 "row": row,
207 "col": col,
208 "rowsize": row_size,
209 "colsize": col_size,
210 "alignmentrow": alignment_row,
211 "alignmentcol": alignment_col
212 }
213 }))?;
214 Ok(())
215 }
216
217 /// Get the dimensions (width, height) of this view in pixels
218 /// Returns (width, height)
219 pub fn get_dimensions(&self, activity: &mut Activity) -> Result<(i32, i32)> {
220 let response = activity.send_read(&json!({
221 "method": "getDimensions",
222 "params": {
223 "aid": activity.id(),
224 "id": self.id
225 }
226 }))?;
227
228 // Response is an array [width, height]
229 if let Some(arr) = response.as_array() {
230 let width = arr.get(0).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
231 let height = arr.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
232 Ok((width, height))
233 } else {
234 Ok((0, 0))
235 }
236 }
237
238 /// Set the background color of this view
239 ///
240 /// Color format: 0xAARRGGBB (alpha, red, green, blue in hexadecimal)
241 ///
242 /// # Examples
243 ///
244 /// ```rust,no_run
245 /// # use termux_gui::{Activity, Result};
246 /// # fn example(activity: &mut Activity, view_id: i64) -> Result<()> {
247 /// // Solid red
248 /// view.set_background_color(activity, 0xFFFF0000u32 as i32)?;
249 ///
250 /// // Semi-transparent blue
251 /// view.set_background_color(activity, 0x800000FFu32 as i32)?;
252 ///
253 /// // White
254 /// view.set_background_color(activity, 0xFFFFFFFFu32 as i32)?;
255 /// # Ok(())
256 /// # }
257 /// ```
258 pub fn set_background_color(&self, activity: &mut Activity, color: i32) -> Result<()> {
259 activity.send(&json!({
260 "method": "setBackgroundColor",
261 "params": {
262 "aid": activity.id(),
263 "id": self.id,
264 "color": color
265 }
266 }))?;
267 Ok(())
268 }
269}