mirror of
https://github.com/m1ngsama/TUT.git
synced 2026-02-08 00:54:05 +00:00
Compare commits
2 commits
4aae1fa7dc
...
be6cc4ca44
| Author | SHA1 | Date | |
|---|---|---|---|
| be6cc4ca44 | |||
| 159e299e96 |
5 changed files with 192 additions and 37 deletions
24
KEYBOARD.md
24
KEYBOARD.md
|
|
@ -7,6 +7,7 @@
|
|||
|-----|--------|
|
||||
| `o` | Open address bar (type URL and press Enter) |
|
||||
| `Backspace` | Go back |
|
||||
| `f` | Go forward |
|
||||
| `r` or `F5` | Refresh current page |
|
||||
| `q` or `Esc` or `F10` | Quit browser |
|
||||
|
||||
|
|
@ -59,6 +60,18 @@
|
|||
# 4. Press 'Esc' to cancel
|
||||
```
|
||||
|
||||
### In-Page Search
|
||||
```bash
|
||||
# Search for text on current page
|
||||
# 1. Press '/' to open search bar
|
||||
# 2. Type your search query (case-insensitive)
|
||||
# 3. Press 'Enter' to find all matches
|
||||
# 4. Press 'n' to go to next match
|
||||
# 5. Press 'N' to go to previous match
|
||||
# 6. Matches are highlighted (yellow = current, blue = other matches)
|
||||
# 7. Status bar shows "Match X/Y" count
|
||||
```
|
||||
|
||||
## 🎨 UI Elements
|
||||
|
||||
### Top Bar
|
||||
|
|
@ -92,17 +105,22 @@
|
|||
4. **Efficient Browsing**: Use `g` to jump to top, `G` to jump to bottom
|
||||
5. **Address Bar**: Type `o` quickly to enter a new URL
|
||||
|
||||
### Search
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `/` | Start search (type query and press Enter) |
|
||||
| `n` | Next search result |
|
||||
| `N` | Previous search result |
|
||||
| `Esc` | Cancel search |
|
||||
|
||||
## 🐛 Known Limitations
|
||||
|
||||
- Ctrl+L not yet working for address bar (use 'o' instead)
|
||||
- Forward navigation not yet implemented
|
||||
- No search functionality yet (/ key)
|
||||
- No bookmarks yet (Ctrl+D)
|
||||
- No history panel yet (F3)
|
||||
|
||||
## 🚀 Coming Soon
|
||||
|
||||
- [ ] In-page search (`/` to search, `n`/`N` to navigate results)
|
||||
- [ ] Bookmarks (add, remove, list)
|
||||
- [ ] History (view and navigate)
|
||||
- [ ] Better link highlighting
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ cmake --build build -j$(nproc)
|
|||
| `g` | Go to top |
|
||||
| `G` | Go to bottom |
|
||||
| `Backspace` | Go back |
|
||||
| `f` | Go forward |
|
||||
|
||||
### Links
|
||||
| Key | Action |
|
||||
|
|
|
|||
35
STATUS.md
35
STATUS.md
|
|
@ -34,9 +34,10 @@
|
|||
- **Content Scrolling** - j/k, g/G, Space/b for navigation
|
||||
- **Link Navigation** - Tab, number keys (1-9), Enter to follow
|
||||
- **Address Bar** - 'o' to open, type URL, Enter to navigate
|
||||
- **Browser Controls** - Backspace to go back, r/F5 to refresh
|
||||
- **Real-time Status** - Load stats, scroll position, selected link
|
||||
- **Visual Feedback** - Navigation button states, link highlighting
|
||||
- **Browser Controls** - Backspace to go back, 'f' to go forward, r/F5 to refresh
|
||||
- **In-Page Search** - '/' to search, n/N to navigate results, highlighted matches
|
||||
- **Real-time Status** - Load stats, scroll position, selected link, search results
|
||||
- **Visual Feedback** - Navigation button states, link highlighting, search highlighting
|
||||
|
||||
### Build & Deployment
|
||||
- ✅ Binary size: **827KB** (well under 1MB target!)
|
||||
|
|
@ -58,15 +59,6 @@
|
|||
- No visual history panel (F3)
|
||||
- No persistence across sessions
|
||||
|
||||
- ⚠️ **Search** - Not implemented
|
||||
- / search command not working
|
||||
- n/N navigation not working
|
||||
- No highlight of matches
|
||||
|
||||
- ⚠️ **Forward Navigation** - Not yet wired up
|
||||
- Forward button shows but doesn't work
|
||||
- Engine supports it, just needs UI connection
|
||||
|
||||
### Feature Gaps
|
||||
- ⚠️ No form support (input fields, buttons, etc.)
|
||||
- ⚠️ No image rendering (even ASCII art)
|
||||
|
|
@ -75,25 +67,15 @@
|
|||
|
||||
## 🎯 Next Steps Priority
|
||||
|
||||
### Phase 1: Polish Interactive Features (High Priority)
|
||||
### Phase 1: Enhanced UX (High Priority)
|
||||
|
||||
1. **Wire Up Forward Navigation** (src/main.cpp)
|
||||
- Connect forward button click to engine.goForward()
|
||||
- Add keyboard shortcut (maybe Shift+Backspace or Alt+→)
|
||||
|
||||
### Phase 2: Enhanced UX (Medium Priority)
|
||||
4. **Implement Search** (src/ui/content_view.cpp)
|
||||
- Add / to start search
|
||||
- Highlight matches
|
||||
- n/N to navigate results
|
||||
|
||||
5. **Add Bookmark System** (new files)
|
||||
1. **Add Bookmark System** (new files)
|
||||
- Implement bookmark storage (JSON file)
|
||||
- Create bookmark panel UI
|
||||
- Add Ctrl+D to bookmark
|
||||
- F2 to view bookmarks
|
||||
|
||||
6. **Add History** (new files)
|
||||
2. **Add History** (new files)
|
||||
- Implement history storage (JSON file)
|
||||
- Create history panel UI
|
||||
- F3 to view history
|
||||
|
|
@ -131,6 +113,9 @@ Interactive test:
|
|||
✅ Press '1' to jump to link 1 - WORKS
|
||||
✅ Enter to follow link - WORKS
|
||||
✅ Backspace to go back - WORKS
|
||||
✅ 'f' to go forward - WORKS
|
||||
✅ '/' to search - WORKS
|
||||
✅ 'n'/'N' to navigate search results - WORKS
|
||||
✅ 'r' to refresh - WORKS
|
||||
✅ 'o' to open address bar - WORKS
|
||||
```
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ void printHelp(const char* prog_name) {
|
|||
<< " Shift+Tab Previous link\n"
|
||||
<< " Enter Follow link\n"
|
||||
<< " Backspace Go back\n"
|
||||
<< " f Go forward\n"
|
||||
<< " / Search in page\n"
|
||||
<< " n/N Next/previous search result\n"
|
||||
<< " Ctrl+L Focus address bar\n"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace tut {
|
||||
|
||||
|
|
@ -40,6 +41,12 @@ public:
|
|||
// Split content into lines for scrolling
|
||||
std::vector<std::string> content_lines_;
|
||||
|
||||
// Search state
|
||||
bool search_mode_{false};
|
||||
std::string search_query_;
|
||||
std::vector<int> search_matches_; // Line indices with matches
|
||||
int current_match_{-1}; // Index into search_matches_
|
||||
|
||||
void setContent(const std::string& content) {
|
||||
content_lines_.clear();
|
||||
std::istringstream iss(content);
|
||||
|
|
@ -48,6 +55,12 @@ public:
|
|||
content_lines_.push_back(line);
|
||||
}
|
||||
scroll_offset_ = 0;
|
||||
|
||||
// Clear search state when new content is loaded
|
||||
search_mode_ = false;
|
||||
search_query_.clear();
|
||||
search_matches_.clear();
|
||||
current_match_ = -1;
|
||||
}
|
||||
|
||||
void scrollDown(int lines = 1) {
|
||||
|
|
@ -79,6 +92,54 @@ public:
|
|||
selected_link_ = static_cast<int>(links_.size()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void executeSearch() {
|
||||
search_matches_.clear();
|
||||
current_match_ = -1;
|
||||
|
||||
if (search_query_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert search query to lowercase for case-insensitive search
|
||||
std::string query_lower = search_query_;
|
||||
std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), ::tolower);
|
||||
|
||||
// Find all lines containing the search query
|
||||
for (size_t i = 0; i < content_lines_.size(); ++i) {
|
||||
std::string line_lower = content_lines_[i];
|
||||
std::transform(line_lower.begin(), line_lower.end(), line_lower.begin(), ::tolower);
|
||||
|
||||
if (line_lower.find(query_lower) != std::string::npos) {
|
||||
search_matches_.push_back(static_cast<int>(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!search_matches_.empty()) {
|
||||
current_match_ = 0;
|
||||
// Scroll to first match
|
||||
scroll_offset_ = search_matches_[0];
|
||||
}
|
||||
}
|
||||
|
||||
void nextMatch() {
|
||||
if (search_matches_.empty()) return;
|
||||
|
||||
current_match_ = (current_match_ + 1) % static_cast<int>(search_matches_.size());
|
||||
// Scroll to the match
|
||||
scroll_offset_ = search_matches_[current_match_];
|
||||
}
|
||||
|
||||
void previousMatch() {
|
||||
if (search_matches_.empty()) return;
|
||||
|
||||
current_match_--;
|
||||
if (current_match_ < 0) {
|
||||
current_match_ = static_cast<int>(search_matches_.size()) - 1;
|
||||
}
|
||||
// Scroll to the match
|
||||
scroll_offset_ = search_matches_[current_match_];
|
||||
}
|
||||
};
|
||||
|
||||
MainWindow::MainWindow() : impl_(std::make_unique<Impl>()) {}
|
||||
|
|
@ -99,6 +160,11 @@ int MainWindow::run() {
|
|||
auto address_input = Input(&address_content, "Enter URL...");
|
||||
bool address_focused = false;
|
||||
|
||||
// 搜索输入
|
||||
std::string search_content;
|
||||
auto search_input = Input(&search_content, "Search...");
|
||||
bool search_focused = false;
|
||||
|
||||
// 内容渲染器
|
||||
auto content_renderer = Renderer([this] {
|
||||
Elements lines;
|
||||
|
|
@ -115,7 +181,28 @@ int MainWindow::run() {
|
|||
static_cast<int>(impl_->content_lines_.size()));
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
lines.push_back(text(impl_->content_lines_[i]));
|
||||
// Check if this line is a search match
|
||||
bool is_match = false;
|
||||
bool is_current_match = false;
|
||||
|
||||
if (!impl_->search_matches_.empty()) {
|
||||
auto it = std::find(impl_->search_matches_.begin(),
|
||||
impl_->search_matches_.end(), i);
|
||||
if (it != impl_->search_matches_.end()) {
|
||||
is_match = true;
|
||||
int match_index = std::distance(impl_->search_matches_.begin(), it);
|
||||
is_current_match = (match_index == impl_->current_match_);
|
||||
}
|
||||
}
|
||||
|
||||
// Render line with appropriate highlighting
|
||||
auto line_element = text(impl_->content_lines_[i]);
|
||||
if (is_current_match) {
|
||||
line_element = line_element | bgcolor(Color::Yellow) | color(Color::Black);
|
||||
} else if (is_match) {
|
||||
line_element = line_element | bgcolor(Color::Blue) | color(Color::White);
|
||||
}
|
||||
lines.push_back(line_element);
|
||||
}
|
||||
|
||||
// Scroll indicator
|
||||
|
|
@ -135,7 +222,16 @@ int MainWindow::run() {
|
|||
auto status_panel = Renderer([this] {
|
||||
Elements status_items;
|
||||
|
||||
if (impl_->loading_) {
|
||||
// Search status takes priority
|
||||
if (!impl_->search_matches_.empty()) {
|
||||
std::string search_info = "🔍 Match " +
|
||||
std::to_string(impl_->current_match_ + 1) + "/" +
|
||||
std::to_string(impl_->search_matches_.size()) +
|
||||
" for \"" + impl_->search_query_ + "\"";
|
||||
status_items.push_back(text(search_info) | color(Color::Yellow));
|
||||
} else if (impl_->search_mode_ && !impl_->search_query_.empty()) {
|
||||
status_items.push_back(text("🔍 No matches for \"" + impl_->search_query_ + "\"") | dim);
|
||||
} else if (impl_->loading_) {
|
||||
status_items.push_back(text("⏳ Loading...") | dim);
|
||||
} else if (impl_->load_time_ > 0) {
|
||||
std::string stats = "⬇ " + std::to_string(impl_->load_bytes_ / 1024) + " KB " +
|
||||
|
|
@ -146,7 +242,8 @@ int MainWindow::run() {
|
|||
status_items.push_back(text("Ready") | dim);
|
||||
}
|
||||
|
||||
if (impl_->selected_link_ >= 0 && impl_->selected_link_ < static_cast<int>(impl_->links_.size())) {
|
||||
if (impl_->selected_link_ >= 0 && impl_->selected_link_ < static_cast<int>(impl_->links_.size()) &&
|
||||
impl_->search_matches_.empty()) {
|
||||
status_items.push_back(separator());
|
||||
std::string link_info = "[" + std::to_string(impl_->selected_link_ + 1) + "] " +
|
||||
impl_->links_[impl_->selected_link_].url;
|
||||
|
|
@ -158,9 +255,18 @@ int MainWindow::run() {
|
|||
|
||||
// 主布局
|
||||
auto main_renderer = Renderer([&] {
|
||||
return vbox({
|
||||
// 顶部栏
|
||||
hbox({
|
||||
Elements top_bar_elements;
|
||||
|
||||
if (search_focused) {
|
||||
// Show search bar when in search mode
|
||||
top_bar_elements.push_back(hbox({
|
||||
text("🔍 "),
|
||||
search_input->Render() | flex | border | focus,
|
||||
text(" [Esc to cancel]") | dim,
|
||||
}));
|
||||
} else {
|
||||
// Normal address bar
|
||||
top_bar_elements.push_back(hbox({
|
||||
text(impl_->can_go_back_ ? "[◀]" : "[◀]") | (impl_->can_go_back_ ? bold : dim),
|
||||
text(" "),
|
||||
text(impl_->can_go_forward_ ? "[▶]" : "[▶]") | (impl_->can_go_forward_ ? bold : dim),
|
||||
|
|
@ -172,7 +278,12 @@ int MainWindow::run() {
|
|||
text("[⚙]") | bold,
|
||||
text(" "),
|
||||
text("[?]") | bold,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
return vbox({
|
||||
// 顶部栏
|
||||
vbox(top_bar_elements),
|
||||
separator(),
|
||||
// 内容区
|
||||
content_renderer->Render() | flex,
|
||||
|
|
@ -235,11 +346,44 @@ int MainWindow::run() {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Don't handle other keys if address bar is focused
|
||||
if (address_focused) {
|
||||
// Search mode activation
|
||||
if (event == Event::Character('/') && !address_focused && !search_focused) {
|
||||
search_focused = true;
|
||||
search_content.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Execute search
|
||||
if (event == Event::Return && search_focused) {
|
||||
impl_->search_mode_ = true;
|
||||
impl_->search_query_ = search_content;
|
||||
impl_->executeSearch();
|
||||
search_focused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit search mode
|
||||
if (event == Event::Escape && search_focused) {
|
||||
search_focused = false;
|
||||
search_content.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't handle other keys if address bar or search is focused
|
||||
if (address_focused || search_focused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Navigate search results (only when not in input mode)
|
||||
if (event == Event::Character('n') && !impl_->search_matches_.empty()) {
|
||||
impl_->nextMatch();
|
||||
return true;
|
||||
}
|
||||
if (event == Event::Character('N') && !impl_->search_matches_.empty()) {
|
||||
impl_->previousMatch();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scrolling
|
||||
if (event == Event::Character('j') || event == Event::ArrowDown) {
|
||||
impl_->scrollDown(1);
|
||||
|
|
@ -309,6 +453,12 @@ int MainWindow::run() {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (event == Event::Character('f') && impl_->can_go_forward_) {
|
||||
if (impl_->on_event_) {
|
||||
impl_->on_event_(WindowEvent::Forward);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Refresh
|
||||
if (event == Event::Character('r') || event == Event::F5) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue