From 3e7ec0bb8f74f89b5b1abe397c9210e8ab986ed2 Mon Sep 17 00:00:00 2001 From: 2b-zipper <119087427+2b-zipper@users.noreply.github.com> Date: Sat, 13 Dec 2025 17:07:17 +0900 Subject: [PATCH 1/4] Supports sorting feature for live --- source/scenes/channel.cpp | 87 ++++++++++++++++++++++++++++++- source/youtube_parser/channel.cpp | 28 +++++++++- source/youtube_parser/parser.hpp | 4 ++ 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/source/scenes/channel.cpp b/source/scenes/channel.cpp index 90b84bb7..c21efb16 100644 --- a/source/scenes/channel.cpp +++ b/source/scenes/channel.cpp @@ -42,6 +42,8 @@ int VIDEO_LIST_Y_HIGH = 240; int cur_video_sort_type = 0; // 0: newest, 1: popular, 2: oldest int video_sort_request = -1; +int cur_streams_sort_type = 0; // 0: newest, 1: popular, 2: oldest +int streams_sort_request = -1; int cur_shorts_sort_type = 0; // 0: newest, 1: popular, 2: oldest int shorts_sort_request = -1; @@ -50,6 +52,7 @@ ImageView *banner_view; ChannelView *channel_view; Tab2View *tab_view; SelectorView *video_sort_selector; +SelectorView *streams_sort_selector; SelectorView *shorts_sort_selector; // anonymous VerticalListView VerticalListView *video_list_view; @@ -105,6 +108,15 @@ void Channel_init(void) { video_sort_request = cur_video_sort_type = view.selected_button; }); + streams_sort_selector = (new SelectorView(0, 0, 320, MIDDLE_FONT_INTERVAL, false)) + ->set_texts({(std::function)[]() { return LOCALIZED(LATEST); }, + (std::function)[]() { return LOCALIZED(POPULAR); }, + (std::function)[]() { return LOCALIZED(OLDEST); }}, + cur_streams_sort_type) + ->set_on_change([](const SelectorView &view) { + streams_sort_request = cur_streams_sort_type = view.selected_button; + }); + shorts_sort_selector = (new SelectorView(0, 0, 320, MIDDLE_FONT_INTERVAL, false)) ->set_texts({(std::function)[]() { return LOCALIZED(LATEST); }, (std::function)[]() { return LOCALIZED(POPULAR); }, @@ -201,7 +213,10 @@ void Channel_init(void) { ->set_views( {(new HorizontalListView(0, 0, MIDDLE_FONT_INTERVAL))->set_views({video_sort_selector}), (new RuleView(0, 0, 320, 2)), video_list_view, video_load_more_view}), - (new VerticalListView(0, 0, 320))->set_views({stream_list_view, stream_load_more_view}), + (new VerticalListView(0, 0, 320)) + ->set_views( + {(new HorizontalListView(0, 0, MIDDLE_FONT_INTERVAL))->set_views({streams_sort_selector}), + (new RuleView(0, 0, 320, 2)), stream_list_view, stream_load_more_view}), (new VerticalListView(0, 0, 320)) ->set_views( {(new HorizontalListView(0, 0, MIDDLE_FONT_INTERVAL))->set_views({shorts_sort_selector}), @@ -249,6 +264,9 @@ void Channel_resume(std::string arg) { if (video_sort_selector) { video_sort_selector->selected_button = cur_video_sort_type; } + if (streams_sort_selector) { + streams_sort_selector->selected_button = cur_streams_sort_type; + } if (shorts_sort_selector) { shorts_sort_selector->selected_button = cur_shorts_sort_type; } @@ -463,6 +481,10 @@ static void load_channel(void *) { if (video_sort_selector) { video_sort_selector->selected_button = cur_video_sort_type; } + cur_streams_sort_type = channel_info.current_streams_sort_type; + if (streams_sort_selector) { + streams_sort_selector->selected_button = cur_streams_sort_type; + } cur_shorts_sort_type = channel_info.current_shorts_sort_type; if (shorts_sort_selector) { shorts_sort_selector->selected_button = cur_shorts_sort_type; @@ -554,6 +576,20 @@ static void load_channel(void *) { }); } + auto streams_tab_view = dynamic_cast(tab_view->views[1]); + if (!channel_info.streams_sort_token_newest.empty() || !channel_info.streams_sort_token_popular.empty() || + !channel_info.streams_sort_token_oldest.empty()) { + dynamic_cast(streams_tab_view->views[0])->update_y_range(0, MIDDLE_FONT_INTERVAL); + streams_tab_view->views[0]->set_is_visible(true); + dynamic_cast(streams_tab_view->views[1])->update_y_range(0, 2); + streams_tab_view->views[1]->set_is_visible(true); + } else { + dynamic_cast(streams_tab_view->views[0])->update_y_range(0, 0); + streams_tab_view->views[0]->set_is_visible(false); + dynamic_cast(streams_tab_view->views[1])->update_y_range(0, 0); + streams_tab_view->views[1]->set_is_visible(false); + } + // shorts list shorts_list_view->recursive_delete_subviews(); if (result.shorts.size() > 0) { @@ -699,6 +735,15 @@ static void load_channel_stream(void *) { channel_info.streams = streams_result.streams; channel_info.streams_continue_token = streams_result.streams_continue_token; channel_info.streams_loaded = true; + // Update sort tokens + channel_info.streams_sort_token_newest = streams_result.streams_sort_token_newest; + channel_info.streams_sort_token_popular = streams_result.streams_sort_token_popular; + channel_info.streams_sort_token_oldest = streams_result.streams_sort_token_oldest; + channel_info.current_streams_sort_type = streams_result.current_streams_sort_type; + cur_streams_sort_type = streams_result.current_streams_sort_type; + if (streams_sort_selector) { + streams_sort_selector->selected_button = cur_streams_sort_type; + } if (streams_result.error == "") { channel_info_cache[channel_info.url_original] = channel_info; } @@ -715,6 +760,20 @@ static void load_channel_stream(void *) { stream_load_more_view->set_is_visible(false); } + auto streams_tab_view = dynamic_cast(tab_view->views[1]); + if (!channel_info.streams_sort_token_newest.empty() || !channel_info.streams_sort_token_popular.empty() || + !channel_info.streams_sort_token_oldest.empty()) { + dynamic_cast(streams_tab_view->views[0])->update_y_range(0, MIDDLE_FONT_INTERVAL); + streams_tab_view->views[0]->set_is_visible(true); + dynamic_cast(streams_tab_view->views[1])->update_y_range(0, 2); + streams_tab_view->views[1]->set_is_visible(true); + } else { + dynamic_cast(streams_tab_view->views[0])->update_y_range(0, 0); + streams_tab_view->views[0]->set_is_visible(false); + dynamic_cast(streams_tab_view->views[1])->update_y_range(0, 0); + streams_tab_view->views[1]->set_is_visible(false); + } + var_need_refresh = true; resource_lock.unlock(); } @@ -1113,6 +1172,32 @@ void Channel_draw(void) { video_sort_request = -1; } + if (streams_sort_request != -1 && tab_view && tab_view->selected_tab == 1) { + std::string sort_token; + if (streams_sort_request == 0) { + sort_token = channel_info.streams_sort_token_newest; + } else if (streams_sort_request == 1) { + sort_token = channel_info.streams_sort_token_popular; + } else if (streams_sort_request == 2) { + sort_token = channel_info.streams_sort_token_oldest; + } + + if (!sort_token.empty()) { + stream_list_view->recursive_delete_subviews(); + stream_list_view->views.clear(); + channel_info.streams.clear(); + channel_info.streams_continue_token = sort_token; + channel_info.current_streams_sort_type = streams_sort_request; + channel_info_cache[cur_channel_url] = channel_info; + + if (!is_async_task_running(load_channel_stream_more)) { + queue_async_task(load_channel_stream_more, NULL); + } + } + + streams_sort_request = -1; + } + if (shorts_sort_request != -1 && tab_view && tab_view->selected_tab == 2) { std::string sort_token; if (shorts_sort_request == 0) { diff --git a/source/youtube_parser/channel.cpp b/source/youtube_parser/channel.cpp index 2c7ae954..22cca439 100644 --- a/source/youtube_parser/channel.cpp +++ b/source/youtube_parser/channel.cpp @@ -82,6 +82,24 @@ static void parse_channel_data(RJson data, YouTubeChannelDetail &res) { break; } } + } else if (is_streams_tab) { + res.streams_sort_token_newest = + chips[0]["chipCloudChipRenderer"]["navigationEndpoint"]["continuationCommand"]["token"] + .string_value(); + res.streams_sort_token_popular = + chips[1]["chipCloudChipRenderer"]["navigationEndpoint"]["continuationCommand"]["token"] + .string_value(); + res.streams_sort_token_oldest = + chips[2]["chipCloudChipRenderer"]["navigationEndpoint"]["continuationCommand"]["token"] + .string_value(); + + // Detect which sort is currently selected + for (size_t i = 0; i < chips.size() && i < 3; i++) { + if (chips[i]["chipCloudChipRenderer"]["isSelected"].bool_value()) { + res.current_streams_sort_type = i; + break; + } + } } else if (is_shorts_tab) { res.shorts_sort_token_newest = chips[0]["chipCloudChipRenderer"]["navigationEndpoint"]["continuationCommand"]["token"] @@ -316,7 +334,9 @@ YouTubeChannelDetail youtube_load_channel_streams_page(std::string url_or_id) { return res; } Document json_root; - parse_channel_data(get_initial_data(json_root, html), res); + auto initial_data = get_initial_data(json_root, html); + + parse_channel_data(initial_data, res); res.streams_loaded = true; } } else { @@ -505,7 +525,11 @@ void YouTubeChannelDetail::load_more_streams() { streams_continue_token = ""; for (auto i : yt_result["onResponseReceivedActions"].array_items()) { - for (auto j : i["appendContinuationItemsAction"]["continuationItems"].array_items()) { + auto continuation_items = i.has_key("appendContinuationItemsAction") + ? i["appendContinuationItemsAction"]["continuationItems"] + : i["reloadContinuationItemsCommand"]["continuationItems"]; + + for (auto j : continuation_items.array_items()) { if (j["richItemRenderer"]["content"].has_key("videoWithContextRenderer")) { streams.push_back( parse_succinct_video(j["richItemRenderer"]["content"]["videoWithContextRenderer"])); diff --git a/source/youtube_parser/parser.hpp b/source/youtube_parser/parser.hpp index 5dd56807..cac11627 100644 --- a/source/youtube_parser/parser.hpp +++ b/source/youtube_parser/parser.hpp @@ -206,6 +206,10 @@ struct YouTubeChannelDetail { std::string video_sort_token_popular; std::string video_sort_token_oldest; int current_video_sort_type = 0; // 0: newest, 1: popular, 2: oldest + std::string streams_sort_token_newest; + std::string streams_sort_token_popular; + std::string streams_sort_token_oldest; + int current_streams_sort_type = 0; // 0: newest, 1: popular, 2: oldest std::string shorts_sort_token_newest; std::string shorts_sort_token_popular; std::string shorts_sort_token_oldest; From 64d8ce9ee7942fe31072fd5fbb4b429232d54e34 Mon Sep 17 00:00:00 2001 From: 2b-zipper <119087427+2b-zipper@users.noreply.github.com> Date: Sat, 13 Dec 2025 17:46:19 +0900 Subject: [PATCH 2/4] Fix character rendering for Traditional Chinese and Korean Fixed https://github.com/erievs/FourthTube/issues/127 --- source/ui/draw/draw.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/source/ui/draw/draw.cpp b/source/ui/draw/draw.cpp index c6f7fb4b..f445dafe 100644 --- a/source/ui/draw/draw.cpp +++ b/source/ui/draw/draw.cpp @@ -290,10 +290,24 @@ void Draw(std::string text, float x, float y, float text_size_x, float text_size C2D_Font cur_font = Extfont_is_extfont_loaded(0) ? system_fonts[prev_font_list_num] : NULL; C2D_TextBufClear(c2d_buf); + float actual_text_size_x = text_size_x; + float actual_text_size_y = text_size_y; + float width_scale = 1.0f; + if (prev_font_list_num == 1) { y_offset = 3 * text_size_y; + } else if (prev_font_list_num == 2) { + // Korean font scaling (80% visual size, 115% width) + y_offset = 4 * text_size_y; + actual_text_size_x *= 0.80; + actual_text_size_y *= 0.80; + width_scale = 1.15f; } else if (prev_font_list_num == 3) { + // Traditional Chinese font scaling (80% visual size, 125% width) y_offset = 5 * text_size_y; + actual_text_size_x *= 0.80; + actual_text_size_y *= 0.80; + width_scale = 1.25f; } else { y_offset = 0; } @@ -315,9 +329,10 @@ void Draw(std::string text, float x, float y, float text_size_x, float text_size C2D_TextFontParse(&c2d_text, cur_font, c2d_buf, draw_str.c_str()); C2D_TextOptimize(&c2d_text); - C2D_TextGetDimensions(&c2d_text, text_size_x, text_size_y, &width, &height); - C2D_DrawText(&c2d_text, C2D_WithColor, x, y + y_offset, 0.0, text_size_x, text_size_y, abgr8888); - x += width; + C2D_TextGetDimensions(&c2d_text, actual_text_size_x, actual_text_size_y, &width, &height); + C2D_DrawText(&c2d_text, C2D_WithColor, x, y + y_offset, 0.0, actual_text_size_x, actual_text_size_y, + abgr8888); + x += width * width_scale; } else if (prev_font_list_num == 4) { Extfont_draw_extfonts(draw_part_text + consecutive_start, i - consecutive_start, x, y, text_size_x * 1.56, text_size_y * 1.56, abgr8888, &width); @@ -484,11 +499,11 @@ Result_with_string Draw_load_texture(std::string file_name, int sheet_map_num, C void Draw_touch_pos(void) { if (var_hide_pointer == false) { - Hid_info key; - Util_hid_query_key_state(&key); - if (key.p_touch || key.h_touch) { - Draw_texture(var_square_image[0], DEF_DRAW_RED, key.touch_x - 1, key.touch_y - 1, 3, 3); - } + Hid_info key; + Util_hid_query_key_state(&key); + if (key.p_touch || key.h_touch) { + Draw_texture(var_square_image[0], DEF_DRAW_RED, key.touch_x - 1, key.touch_y - 1, 3, 3); + } } } From c3f658e19adf2aca79d0e8205a4090ab8dbf591e Mon Sep 17 00:00:00 2001 From: 2b-zipper <119087427+2b-zipper@users.noreply.github.com> Date: Sat, 13 Dec 2025 17:56:56 +0900 Subject: [PATCH 3/4] Disable timestamp parsing for community posts --- source/scenes/channel.cpp | 1 + source/ui/views/specialized/post.hpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/scenes/channel.cpp b/source/scenes/channel.cpp index c21efb16..ead7d427 100644 --- a/source/scenes/channel.cpp +++ b/source/scenes/channel.cpp @@ -377,6 +377,7 @@ View *community_post_2_view(const YouTubeChannelDetail::CommunityPost &post) { ->set_time_str(post.time) ->set_upvote_str(post.upvotes_str) ->set_additional_image_url(post.image_url) + ->set_disable_timestamps(true) ->set_content_lines(content_lines) ->set_has_more_replies([]() { return false; }); diff --git a/source/ui/views/specialized/post.hpp b/source/ui/views/specialized/post.hpp index 1b6df25a..53051d9f 100644 --- a/source/ui/views/specialized/post.hpp +++ b/source/ui/views/specialized/post.hpp @@ -81,6 +81,7 @@ struct PostView : public FixedWidthView { size_t replies_shown = 0; bool is_reply = false; bool is_description_mode = false; // For video description display without icon/author + bool disable_timestamps = false; // Disable timestamp parsing for community posts volatile bool is_loading_replies = false; std::function get_has_more_replies; @@ -220,7 +221,9 @@ struct PostView : public FixedWidthView { PostView *set_content_lines(const std::vector &content_lines) { // mandatory this->content_lines = content_lines; this->lines_shown = std::min(3, content_lines.size()); - parse_timestamps_from_content(); + if (!disable_timestamps) { + parse_timestamps_from_content(); + } return this; } PostView *set_has_more_replies(const std::function &get_has_more_replies) { // mandatory @@ -247,6 +250,10 @@ struct PostView : public FixedWidthView { this->on_timestamp_pressed_func = on_timestamp_pressed_func; return this; } + PostView *set_disable_timestamps(bool disable_timestamps) { + this->disable_timestamps = disable_timestamps; + return this; + } void draw_() const override; void update_(Hid_info key) override; From cb2dc0a80a8c6eea1daa1929f325b19148de0f97 Mon Sep 17 00:00:00 2001 From: 2b-zipper <119087427+2b-zipper@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:06:09 +0900 Subject: [PATCH 4/4] Refactor timestamp handling --- source/ui/views/specialized/post.cpp | 161 ++++++++++++--------------- source/ui/views/specialized/post.hpp | 1 + 2 files changed, 74 insertions(+), 88 deletions(-) diff --git a/source/ui/views/specialized/post.cpp b/source/ui/views/specialized/post.cpp index 23bc41c0..78ddd578 100644 --- a/source/ui/views/specialized/post.cpp +++ b/source/ui/views/specialized/post.cpp @@ -15,8 +15,7 @@ void PostView::cancel_all_thumbnail_requests() { } void PostView::draw_() const { int cur_y = y0; - - // Skip author info for description mode + if (!is_description_mode) { if (cur_y < 240 && cur_y + DEFAULT_FONT_INTERVAL > 0) { float x = content_x_pos(); @@ -48,7 +47,7 @@ void PostView::draw_() const { } cur_y += DEFAULT_FONT_INTERVAL; } - + if (!is_description_mode) { cur_y = std::max(cur_y, y0 + get_icon_size() + SMALL_MARGIN); } @@ -65,7 +64,8 @@ void PostView::draw_() const { Draw_x_centered(LOCALIZED(UNSUPPORTED_IMAGE), content_x_pos(), content_x_pos() + COMMUNITY_IMAGE_SIZE, cur_y + COMMUNITY_IMAGE_SIZE * 0.3, 0.5, 0.5, DEFAULT_TEXT_COLOR); } else { - thumbnail_draw(additional_image_handle, content_x_pos(), cur_y, COMMUNITY_IMAGE_SIZE, COMMUNITY_IMAGE_SIZE); + thumbnail_draw(additional_image_handle, content_x_pos(), cur_y, COMMUNITY_IMAGE_SIZE, + COMMUNITY_IMAGE_SIZE); } cur_y += COMMUNITY_IMAGE_SIZE + SMALL_MARGIN; } @@ -75,7 +75,7 @@ void PostView::draw_() const { cur_y += SMALL_MARGIN + additional_video_view->get_height(); } cur_y += SMALL_MARGIN; - + Draw_texture(var_texture_thumb_up[var_night_mode], content_x_pos(), cur_y, 16, 16); Draw(upvote_str, content_x_pos() + 16 + SMALL_MARGIN, cur_y + 1, 0.44, 0.44, LIGHT1_TEXT_COLOR); cur_y += 16 + SMALL_MARGIN; @@ -111,8 +111,7 @@ void PostView::draw_() const { } void PostView::update_(Hid_info key) { int cur_y = y0; - - // Skip author icon handling in description mode + if (!is_description_mode) { bool inside_author_icon = in_range(key.touch_x, x0, std::min(x1, x0 + get_icon_size() + SMALL_MARGIN)) && in_range(key.touch_y, cur_y, cur_y + get_icon_size()); @@ -129,7 +128,6 @@ void PostView::update_(Hid_info key) { cur_y += DEFAULT_FONT_INTERVAL; } - // timestamp touch handling int content_line_y = cur_y; for (size_t line = 0; line < lines_shown; line++) { if (content_line_y < 240 && content_line_y + DEFAULT_FONT_INTERVAL > 0) { @@ -171,7 +169,7 @@ void PostView::update_(Hid_info key) { additional_video_view->update(key, content_x_pos(), cur_y); cur_y += additional_video_view->get_height() + SMALL_MARGIN; } - + cur_y += 16 + SMALL_MARGIN * 2; if (replies_shown) { @@ -206,9 +204,10 @@ void PostView::update_(Hid_info key) { std::string message = is_loading_replies ? LOCALIZED(LOADING) : replies_shown ? LOCALIZED(SHOW_MORE_REPLIES) : LOCALIZED(SHOW_REPLIES); - bool inside_show_more_replies = in_range(key.touch_x, content_x_pos(), - std::min(x1, content_x_pos() + Draw_get_width(message, 0.5))) && - in_range(key.touch_y, cur_y, cur_y + DEFAULT_FONT_INTERVAL + 1); + bool inside_show_more_replies = + in_range(key.touch_x, content_x_pos(), + std::min(x1, content_x_pos() + Draw_get_width(message, 0.5))) && + in_range(key.touch_y, cur_y, cur_y + DEFAULT_FONT_INTERVAL + 1); if (key.p_touch && inside_show_more_replies) { show_more_replies_holding = true; @@ -234,36 +233,34 @@ void PostView::update_(Hid_info key) { void PostView::parse_timestamps_from_content() { timestamps.clear(); - + for (size_t line_idx = 0; line_idx < content_lines.size(); line_idx++) { - const std::string& line_text = content_lines[line_idx]; - if (line_text.length() < 4) continue; - - int search_pos = 0; + const std::string &line_text = content_lines[line_idx]; + if (line_text.length() < 4) { + continue; + } - while (search_pos < (int)line_text.length() - 3) { + for (int search_pos = 0; search_pos < (int)line_text.length() - 3;) { int timestamp_start, timestamp_end; double timestamp_seconds; - - int found_pos = Util_find_timestamp_in_text( - line_text, search_pos, ×tamp_start, ×tamp_end, ×tamp_seconds); - - if (found_pos == -1) { + + if (Util_find_timestamp_in_text(line_text, search_pos, ×tamp_start, ×tamp_end, + ×tamp_seconds) == -1) { break; } - if (timestamp_start >= 0 && timestamp_end > timestamp_start && - timestamp_end <= (int)line_text.length() && timestamp_seconds >= 0.0) { + if (timestamp_start >= 0 && timestamp_end > timestamp_start && timestamp_end <= (int)line_text.length() && + timestamp_seconds >= 0.0) { timestamps.emplace_back(line_idx, timestamp_start, timestamp_end, timestamp_seconds); } - + search_pos = std::max(timestamp_end, search_pos + 1); } } } void PostView::reset_timestamp_holding_status() { - for (auto& timestamp : timestamps) { + for (auto ×tamp : timestamps) { timestamp.is_holding = false; } } @@ -272,19 +269,21 @@ void PostView::draw_content_line_with_timestamps(size_t line_index, float x, flo if (line_index >= content_lines.size()) { return; } - - const std::string& line = content_lines[line_index]; - - std::vector line_timestamps; - for (const auto& timestamp : timestamps) { - if (timestamp.line_index == (int)line_index) { - if (timestamp.start_pos >= 0 && timestamp.end_pos > timestamp.start_pos && - timestamp.start_pos < (int)line.length() && timestamp.end_pos <= (int)line.length()) { - line_timestamps.push_back(×tamp); - } + + const std::string &line = content_lines[line_index]; + + auto is_valid_timestamp = [&](const TimestampInfo &ts) { + return ts.line_index == (int)line_index && ts.start_pos >= 0 && ts.end_pos > ts.start_pos && + ts.start_pos < (int)line.length() && ts.end_pos <= (int)line.length(); + }; + + std::vector line_timestamps; + for (const auto ×tamp : timestamps) { + if (is_valid_timestamp(timestamp)) { + line_timestamps.push_back(×tamp); } } - + if (line_timestamps.empty()) { Draw(line, x, y, 0.5, 0.5, DEFAULT_TEXT_COLOR); return; @@ -292,45 +291,34 @@ void PostView::draw_content_line_with_timestamps(size_t line_index, float x, flo int last_end = 0; float current_x = x; - - for (const auto* timestamp : line_timestamps) { - if (timestamp->start_pos < 0 || timestamp->end_pos <= timestamp->start_pos || - timestamp->end_pos > (int)line.length()) { - continue; - } - + + auto draw_text = [&](const std::string &text, int color, bool underline = false) { + Draw(text, current_x, y, 0.5, 0.5, color); + float width = Draw_get_width(text, 0.5); + if (underline) { + Draw_line(current_x, y + DEFAULT_FONT_INTERVAL, color, current_x + width, y + DEFAULT_FONT_INTERVAL, color, + 1); + } + current_x += width; + }; + + for (const auto *timestamp : line_timestamps) { if (timestamp->start_pos > last_end) { int before_len = std::min(timestamp->start_pos - last_end, (int)line.length() - last_end); if (before_len > 0) { - std::string before_text = line.substr(last_end, before_len); - Draw(before_text, current_x, y, 0.5, 0.5, DEFAULT_TEXT_COLOR); - current_x += Draw_get_width(before_text, 0.5); + draw_text(line.substr(last_end, before_len), DEFAULT_TEXT_COLOR); } } - int text_len = std::min(timestamp->end_pos - timestamp->start_pos, - (int)line.length() - timestamp->start_pos); + int text_len = std::min(timestamp->end_pos - timestamp->start_pos, (int)line.length() - timestamp->start_pos); if (text_len > 0) { - std::string timestamp_text = line.substr(timestamp->start_pos, text_len); - Draw(timestamp_text, current_x, y, 0.5, 0.5, COLOR_LINK); - - if (timestamp->is_holding) { - float timestamp_width = Draw_get_width(timestamp_text, 0.5); - Draw_line(current_x, y + DEFAULT_FONT_INTERVAL, COLOR_LINK, - current_x + timestamp_width, y + DEFAULT_FONT_INTERVAL, COLOR_LINK, 1); - } - - current_x += Draw_get_width(timestamp_text, 0.5); + draw_text(line.substr(timestamp->start_pos, text_len), COLOR_LINK, timestamp->is_holding); } last_end = std::max(last_end, timestamp->end_pos); } if (last_end < (int)line.length()) { - int after_len = (int)line.length() - last_end; - if (after_len > 0) { - std::string after_text = line.substr(last_end, after_len); - Draw(after_text, current_x, y, 0.5, 0.5, DEFAULT_TEXT_COLOR); - } + draw_text(line.substr(last_end), DEFAULT_TEXT_COLOR); } } @@ -338,45 +326,42 @@ void PostView::handle_timestamp_touch(Hid_info key, size_t line_index, float lin if (line_index >= content_lines.size()) { return; } - - const std::string& line = content_lines[line_index]; - for (auto& timestamp : timestamps) { + const std::string &line = content_lines[line_index]; + + for (auto ×tamp : timestamps) { if (timestamp.line_index != (int)line_index) { continue; } if (timestamp.start_pos < 0 || timestamp.end_pos <= timestamp.start_pos || - timestamp.start_pos >= (int)line.length() || timestamp.end_pos > (int)line.length()) { + timestamp.start_pos >= (int)line.length() || timestamp.end_pos > (int)line.length()) { continue; } float timestamp_x = line_x; + if (timestamp.start_pos > 0) { + timestamp_x += Draw_get_width(line.substr(0, timestamp.start_pos), 0.5); + } - if (timestamp.start_pos > 0 && timestamp.start_pos <= (int)line.length()) { - std::string before_text = line.substr(0, std::min(timestamp.start_pos, (int)line.length())); - timestamp_x += Draw_get_width(before_text, 0.5); + int text_len = std::min(timestamp.end_pos - timestamp.start_pos, (int)line.length() - timestamp.start_pos); + if (text_len <= 0) { + continue; } - int text_start = std::max(0, timestamp.start_pos); - int text_len = std::min(timestamp.end_pos - text_start, (int)line.length() - text_start); - if (text_len <= 0) continue; - - std::string timestamp_text = line.substr(text_start, text_len); - float timestamp_width = Draw_get_width(timestamp_text, 0.5); + float timestamp_width = Draw_get_width(line.substr(timestamp.start_pos, text_len), 0.5); + bool inside_timestamp = + in_range(key.touch_x, timestamp_x, std::min(x1, timestamp_x + timestamp_width)) && + in_range(key.touch_y, line_y, line_y + DEFAULT_FONT_INTERVAL); - bool inside_timestamp = in_range(key.touch_x, timestamp_x, std::min(x1, timestamp_x + timestamp_width)) && - in_range(key.touch_y, line_y, line_y + DEFAULT_FONT_INTERVAL); - if (key.p_touch && inside_timestamp) { timestamp.is_holding = true; - } - - if (key.touch_x == -1 && timestamp.is_holding && on_timestamp_pressed_func) { - on_timestamp_pressed_func(timestamp.seconds); - } - - if (!inside_timestamp) { + } else if (key.touch_x == -1 && timestamp.is_holding) { + if (on_timestamp_pressed_func) { + on_timestamp_pressed_func(timestamp.seconds); + } + timestamp.is_holding = false; + } else if (!inside_timestamp) { timestamp.is_holding = false; } } diff --git a/source/ui/views/specialized/post.hpp b/source/ui/views/specialized/post.hpp index 53051d9f..8f89c4a8 100644 --- a/source/ui/views/specialized/post.hpp +++ b/source/ui/views/specialized/post.hpp @@ -45,6 +45,7 @@ struct PostView : public FixedWidthView { static inline bool in_range(float x, float l, float r) { return x >= l && x < r; } // timestamp helper methods + inline bool is_timestamp_enabled() const { return !disable_timestamps && on_timestamp_pressed_func; } void parse_timestamps_from_content(); void reset_timestamp_holding_status(); void draw_content_line_with_timestamps(size_t line_index, float x, float y) const;