Skip to content

Commit 05588ae

Browse files
AnlangAclaude
andcommitted
Add bit operations, shift/rotate controls, undo/redo, and improve UI feedback
- Add bit_ops core module with hex/bit-string/decimal conversions, toggle, invert, shift (zero-fill & rotate) - Add undo/redo support for bit viewer operations - Add bit-string and decimal input fields to bit viewer page - Improve calc_engine Python/sympy detection with detailed diagnostics and remediation hints - Extract shared UI feedback components (group_panel, show_error, show_empty_state, show_instructions, etc.) - Refactor number_conversion and text_conversion pages to use shared components - Update README with new feature descriptions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5f658c9 commit 05588ae

13 files changed

Lines changed: 1635 additions & 308 deletions

File tree

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 编码转换工具
22

3-
一个用 Rust 和 egui 构建的跨平台编码转换工具,支持多种进制转换、文本编码转换和位操作查看
3+
一个用 Rust 和 egui 构建的跨平台编码转换工具,支持多种进制转换、文本编码转换、位查看与位操作,以及多进制表达式计算
44

55
![License](https://img.shields.io/badge/license-MIT-blue.svg)
66
![Rust](https://img.shields.io/badge/rust-1.70+-orange.svg)
@@ -9,8 +9,9 @@
99

1010
- **进制转换**:二进制 ↔ 十进制 ↔ 十六进制,实时转换
1111
- **文本编码**:ASCII ↔ 十六进制,支持不可打印字符显示
12-
- **位查看器**:可视化位操作,支持点击切换
13-
- **浮点数转换**:IEEE 754 标准,支持 f32 浮点数转换
12+
- **位查看器**:支持十六进制 / 位串 / 十进制联动查看,支持点击切换、按位取反、逻辑移位、循环移位、撤销与重做
13+
- **浮点数转换**:IEEE 754 标准,支持 f32 浮点数转换与分析
14+
- **表达式计算器**:支持二进制、八进制、十进制、十六进制表达式计算,并自动转换显示结果
1415

1516
## 快速开始
1617

@@ -33,10 +34,11 @@ cargo run --release
3334

3435
```
3536
src/
36-
├── app/ # 应用程序层
37-
├── core/ # 核心业务逻辑
38-
├── ui/ # 用户界面层
39-
└── utils/ # 工具函数
37+
├── app/ # 应用程序入口、配置与启动逻辑
38+
├── backend/ # 异步计算与前后端消息通信
39+
├── core/ # 核心业务逻辑(位操作、计算引擎等)
40+
├── frontend/ # 页面状态管理与本地交互逻辑
41+
└── ui/ # 用户界面层与可复用组件
4042
```
4143

4244
## 开发
@@ -46,6 +48,8 @@ src/
4648
cargo test
4749
```
4850

51+
如果你要使用表达式计算器,请确保当前 Python 环境可用;项目会优先查找本地虚拟环境中的 Python,并依赖 `scripts/calc_engine.py` 所需的 `sympy` 包。
52+
4953
## 许可证
5054

5155
MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。

src/backend/messages.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ pub enum BitViewerOperation {
162162
ToggleBit(usize),
163163
/// Invert all bits
164164
InvertAll,
165+
/// Shift all bits to the left by the given count
166+
ShiftLeft(usize),
167+
/// Shift all bits to the right by the given count
168+
ShiftRight(usize),
165169
}
166170

167171
/// Bit viewer request.
@@ -173,7 +177,7 @@ pub struct BitViewerRequest {
173177
pub operation: BitViewerOperation,
174178
/// Hex input (for ParseHex)
175179
pub hex_input: Option<String>,
176-
/// Current binary bits (for ToggleBit/InvertAll)
180+
/// Current binary bits (for bit operations)
177181
pub current_bits: Option<Vec<bool>>,
178182
}
179183

src/backend/worker.rs

Lines changed: 49 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::mpsc::{Receiver, Sender, TryRecvError};
44
use std::thread::{self, JoinHandle};
55

66
use super::messages::*;
7-
use crate::core::calc_engine;
7+
use crate::core::{bit_ops, calc_engine};
88

99
/// Backend processor that handles all computation requests.
1010
pub struct BackendWorker;
@@ -229,105 +229,80 @@ impl BackendWorker {
229229
match req.operation {
230230
BitViewerOperation::ParseHex => {
231231
let hex_input = req.hex_input.unwrap_or_default();
232-
let clean_hex: String = hex_input
233-
.chars()
234-
.filter(|c| c.is_ascii_hexdigit())
235-
.collect::<String>()
236-
.to_uppercase();
237-
238-
if clean_hex.is_empty() {
239-
return BitViewerResponse {
232+
match bit_ops::parse_hex_input(&hex_input) {
233+
Ok(parsed) => BitViewerResponse {
240234
id: req.id,
241-
hex_input: String::new(),
242-
binary_bits: Vec::new(),
243-
error: Some("输入为空".to_string()),
244-
};
245-
}
246-
247-
// Validate hex characters
248-
if !clean_hex.chars().all(|c| c.is_ascii_hexdigit()) {
249-
return BitViewerResponse {
235+
hex_input: parsed.normalized_hex,
236+
binary_bits: bit_ops::bit_string_to_bits(&parsed.bit_string)
237+
.unwrap_or_default(),
238+
error: None,
239+
},
240+
Err(error) => BitViewerResponse {
250241
id: req.id,
251242
hex_input,
252243
binary_bits: Vec::new(),
253-
error: Some("无效的十六进制字符".to_string()),
254-
};
255-
}
256-
257-
let mut binary_bits = Vec::new();
258-
for hex_char in clean_hex.chars() {
259-
if let Some(digit) = hex_char.to_digit(16) {
260-
let digit = digit as u8;
261-
for i in (0..4).rev() {
262-
binary_bits.push((digit & (1 << i)) != 0);
263-
}
264-
}
244+
error: Some(error),
245+
},
265246
}
247+
}
248+
BitViewerOperation::ToggleBit(index) => {
249+
let current_bits = req.current_bits.unwrap_or_default();
250+
let bit_string = bit_ops::bits_to_bit_string(&current_bits);
251+
let updated_bit_string =
252+
bit_ops::toggle_bit(&bit_string, index).unwrap_or(bit_string);
266253

267254
BitViewerResponse {
268255
id: req.id,
269-
hex_input: clean_hex,
270-
binary_bits,
256+
hex_input: bit_ops::bit_string_to_hex(&updated_bit_string).unwrap_or_default(),
257+
binary_bits: bit_ops::bit_string_to_bits(&updated_bit_string)
258+
.unwrap_or_default(),
271259
error: None,
272260
}
273261
}
274-
BitViewerOperation::ToggleBit(index) => {
275-
let mut bits = req.current_bits.unwrap_or_default();
276-
if index < bits.len() {
277-
bits[index] = !bits[index];
278-
}
279-
let hex_input = Self::bits_to_hex(&bits);
262+
BitViewerOperation::InvertAll => {
263+
let current_bits = req.current_bits.unwrap_or_default();
264+
let bit_string = bit_ops::bits_to_bit_string(&current_bits);
265+
let updated_bit_string = bit_ops::invert_all(&bit_string).unwrap_or(bit_string);
280266

281267
BitViewerResponse {
282268
id: req.id,
283-
hex_input,
284-
binary_bits: bits,
269+
hex_input: bit_ops::bit_string_to_hex(&updated_bit_string).unwrap_or_default(),
270+
binary_bits: bit_ops::bit_string_to_bits(&updated_bit_string)
271+
.unwrap_or_default(),
285272
error: None,
286273
}
287274
}
288-
BitViewerOperation::InvertAll => {
289-
let mut bits = req.current_bits.unwrap_or_default();
290-
for bit in &mut bits {
291-
*bit = !*bit;
292-
}
293-
let hex_input = Self::bits_to_hex(&bits);
275+
BitViewerOperation::ShiftLeft(count) => {
276+
let current_bits = req.current_bits.unwrap_or_default();
277+
let bit_string = bit_ops::bits_to_bit_string(&current_bits);
278+
let updated_bit_string =
279+
bit_ops::shift_left(&bit_string, count, bit_ops::ShiftMode::ZeroFill)
280+
.unwrap_or(bit_string);
294281

295282
BitViewerResponse {
296283
id: req.id,
297-
hex_input,
298-
binary_bits: bits,
284+
hex_input: bit_ops::bit_string_to_hex(&updated_bit_string).unwrap_or_default(),
285+
binary_bits: bit_ops::bit_string_to_bits(&updated_bit_string)
286+
.unwrap_or_default(),
299287
error: None,
300288
}
301289
}
302-
}
303-
}
304-
305-
fn bits_to_hex(bits: &[bool]) -> String {
306-
if bits.is_empty() {
307-
return String::new();
308-
}
309-
310-
let mut hex_string = String::new();
311-
let mut current_nibble = 0u8;
290+
BitViewerOperation::ShiftRight(count) => {
291+
let current_bits = req.current_bits.unwrap_or_default();
292+
let bit_string = bit_ops::bits_to_bit_string(&current_bits);
293+
let updated_bit_string =
294+
bit_ops::shift_right(&bit_string, count, bit_ops::ShiftMode::ZeroFill)
295+
.unwrap_or(bit_string);
312296

313-
for (i, &bit) in bits.iter().enumerate() {
314-
let bit_pos = 3 - (i % 4);
315-
if bit {
316-
current_nibble |= 1 << bit_pos;
317-
}
318-
319-
if (i + 1) % 4 == 0 {
320-
hex_string.push_str(&format!("{:X}", current_nibble));
321-
current_nibble = 0;
297+
BitViewerResponse {
298+
id: req.id,
299+
hex_input: bit_ops::bit_string_to_hex(&updated_bit_string).unwrap_or_default(),
300+
binary_bits: bit_ops::bit_string_to_bits(&updated_bit_string)
301+
.unwrap_or_default(),
302+
error: None,
303+
}
322304
}
323305
}
324-
325-
// Handle incomplete last nibble
326-
if !bits.len().is_multiple_of(4) {
327-
hex_string.push_str(&format!("{:X}", current_nibble));
328-
}
329-
330-
hex_string
331306
}
332307

333308
fn handle_calculator(req: CalculatorRequest) -> CalculatorResponse {

0 commit comments

Comments
 (0)