use eframe::egui;
use crate::system::{
ThreadOrder,
System,
};
use crate::uistate::{
UIBackend,
UIMode,
ThreadID
};
use crate::strings;
use crate::shortcuts;
use crate::util;
use crate::cpumap::CPUMap;
use crate::uifrontend::UIFrontend;
use crate::topologycache::{
TopologyObjectID,
TopologyObjectInfo,
TopologyObjectChildren,
TopologyObjectLabelStyle
};
use util::ShorcutControllable;
impl eframe::App for CPUMap {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
if self.backend.is_first_update() {
match self.frontend {
UIFrontend::Egui(..) => {
EguiFrontend::setup(ctx);
},
_ => {
panic!("egui update called for non-egui frontend");
}
}
}
self.backend.handle_events(&mut self.system);
match self.frontend {
UIFrontend::Egui(ref mut egui) => {
egui.update( &mut self.backend, ctx, frame, &mut self.system );
},
_ => {
panic!("egui update called for non-egui frontend");
}
}
}
}
pub struct EguiFrontend {
shortcuts: shortcuts::Shortcuts,
show_help: bool,
first_frame: bool,
last_hovered_frame_object: Option<(TopologyObjectID, Option<ThreadID>)>,
}
impl EguiFrontend {
pub fn new() -> Self {
Self {
shortcuts: shortcuts::Shortcuts::new(),
show_help: false,
first_frame: true,
last_hovered_frame_object: None,
}
}
pub fn run(creator: eframe::AppCreator)
-> anyhow::Result<()>
{
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1024.0, 768.0)),
min_window_size: Some(egui::vec2(720.0, 320.0)),
..Default::default()
};
if let Err(e) = eframe::run_native("cpumap", options, creator) {
return Err(anyhow::anyhow!("Failed to initialize GUI: {}", e));
} else {
Ok(())
}
}
pub fn setup(ctx: &egui::Context) {
let small_cache_style = egui::TextStyle::Name("SmallCache".into());
let small_font = egui::FontId {
size: 11.0,
family: egui::FontFamily::Proportional,
};
let thread_view_style = egui::TextStyle::Name("ThreadView".into());
let very_small_font = egui::FontId {
size: 11.0,
family: egui::FontFamily::Proportional,
};
let mut style = (*ctx.style()).clone();
style.text_styles.insert(small_cache_style, small_font);
style.text_styles.insert(thread_view_style, very_small_font);
ctx.set_style(style);
}
pub fn update(&mut self, ui: &mut UIBackend, ctx: &egui::Context, frame: &mut eframe::Frame, system: &mut System) {
self.statusbar(ui, ctx);
self.process_selection_panel(ui, ctx, system);
if frame.info().window_info.size.x >= 800.0 {
self.right_panel(ui, ctx, system);
}
ui.update_current_process_data(system);
self.topology_view(ui, ctx, system);
ui.handle_timeouts(system);
ui.end_update();
self.first_frame = false;
}
fn statusbar(&self, backend: &UIBackend, ctx: &egui::Context) {
egui::TopBottomPanel::bottom("statusbar").show(ctx, |ui| {
ui.add_space(4.0);
ui.label(backend.get_status().as_str());
});
}
fn process_selection_panel(&self, backend: &mut UIBackend, ctx: &egui::Context, system: &System) {
if backend.get_hide_process_selection_panel() {
egui::SidePanel::left("processes_dummy").exact_width(22.0).show(ctx, |ui| {
ui.spacing_mut().button_padding.x = 3.0;
*backend.get_hide_process_selection_panel_mut() =
!ui.small_button(">>").on_hover_text(strings::TTIP_PROCLIST_SHOW).clicked_or_shortcut(ui, self.shortcuts.toggle_left);
});
return;
}
egui::SidePanel::left("processes").width_range(100.0 ..= 600.0).default_width(300.0).show(ctx, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().button_padding.x = 3.0;
*backend.get_hide_process_selection_panel_mut() =
ui.small_button("<<").on_hover_text(strings::TTIP_PROCLIST_HIDE).clicked_or_shortcut(ui, self.shortcuts.toggle_left);
let filter_label = ui.label(strings::filter());
ui.text_edit_singleline(backend.get_filter_mut()).labelled_by(filter_label.id).focus_on_shortcut(ui, self.shortcuts.filter);
});
let processes = backend.get_filtered_user_processes( system );
let total_rows = processes.len();
ui.label(strings::processes(total_rows));
let text_style = egui::TextStyle::Body;
let row_height = ui.text_style_height(&text_style);
self.process_selection_panel_keyboard_control(ui, backend, system);
egui::ScrollArea::both().auto_shrink([false, true]).show_rows(ui, row_height, total_rows, |ui, row_range| {
for row in row_range {
self.process_selection_entry(ui, backend, system, processes[row].0, System::get_process_name(processes[row].1));
}
});
});
}
fn process_selection_entry(&self,
ui: &mut egui::Ui,
backend: &mut UIBackend,
system: &System,
pid: &sysinfo::Pid,
name: &str)
{
let mut selected_pid = backend.get_selected_pid();
let response = ui.horizontal(|ui|{
let start_response = ui.label(format!("{}", pid));
let pid_width = start_response.rect.width();
ui.add_space(f32::max(0.0, 50.0 - pid_width));
let cmd = UIBackend::get_process_cmd(*pid, system);
let response = ui.selectable_value(&mut selected_pid, *pid, name).on_hover_text(name);
let name_width = response.rect.width();
if response.clicked() {
backend.set_next_mode_view( system, selected_pid );
}
ui.add_space(f32::max(0.0, 180.0 - name_width));
ui.label(&cmd).on_hover_text(&cmd);
if *pid == selected_pid && backend.is_mode_changing() {
start_response.scroll_to_me(Some(egui::Align::Center));
}
});
}
fn process_selection_panel_keyboard_control(&self, ui: &egui::Ui, backend: &mut UIBackend, system: &System) -> Option<bool> {
let process_prevnext_pressed =
if ui.input_mut(|i| i.consume_shortcut(&self.shortcuts.process_previous)) { Some(true) }
else if ui.input_mut(|i| i.consume_shortcut(&self.shortcuts.process_next)) { Some(false) }
else { None };
backend.process_selection_panel_keyboard_control(process_prevnext_pressed, system);
process_prevnext_pressed
}
fn right_panel(&mut self, backend: &mut UIBackend, ctx: &egui::Context, system: &System) {
egui::SidePanel::right("right_panel")
.resizable(false)
.width_range(200.0..=290.0)
.show(ctx, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
ui.style_mut().spacing.item_spacing = egui::vec2(1.0, 0.0);
ui.style_mut().override_text_style = Some(egui::TextStyle::Heading);
if ui.add(egui::Button::new("HELP").rounding(egui::Rounding::none())).on_hover_text(strings::TTIP_HELP).clicked_or_shortcut(ui, self.shortcuts.help) {
self.show_help = true;
}
if ui.add(egui::Button::new("CPU").rounding(egui::Rounding::none())).on_hover_text(strings::TTIP_CPU).clicked_or_shortcut(ui, self.shortcuts.cpu) {
self.show_help = false;
}
});
if self.show_help {
egui::ScrollArea::vertical().show(ui, |ui| {
self.help(backend, ui);
});
} else {
self.cpu_information(ui, system);
}
});
}
fn help(&self, backend: &UIBackend, ui: &mut egui::Ui) {
use egui_commonmark::*;
let (mode, mode_shortcuts, is_run) = backend.get_current_mode_help();
let main = format!("{}{}{}\n\
\n\
---\n\
", strings::HELP_MAIN, if is_run {strings::HELP_MAIN_RUN} else {"."}
, strings::HELP_MAIN_SHORTCUTS_GUI);
let mut cache = CommonMarkCache::default();
CommonMarkViewer::new("help-main").show(ui, &mut cache, main.as_str());
CommonMarkViewer::new("help-mode").show(ui, &mut cache, format!("{}\n{}", mode, mode_shortcuts).as_str());
}
fn cpu_information(&self, ui: &mut egui::Ui, system: &System) {
ui.heading(strings::CPU_INFO);
for info in system.cpu_info() {
ui.label(format!("{}", info.name));
}
}
fn topology_view(&mut self, backend: &mut UIBackend, ctx: &egui::Context, system: &mut System) {
egui::TopBottomPanel::bottom("topofooter").show_separator_line(false).show(ctx, |ui| {
self.topology_footer(backend, ui, system);
});
let central_margin = egui::Margin{top:1.0, bottom: 6.0, right: 6.0, left: 6.0};
let central_frame = egui::Frame::central_panel(&ctx.style()).inner_margin(central_margin);
egui::CentralPanel::default().frame(central_frame).show(ctx, |ui| {
egui::ScrollArea::both().show(ui, |ui| {
self.topology_header(backend, ui, system);
ui.horizontal(|ui|{
self.topology_grid(backend, ui, system);
self.topology_side_buttons(backend, ui, system);
});
self.topology_buttons_process(backend, ui, system);
ui.separator();
self.topology_threads(backend, ui, system);
});
});
}
fn topology_footer(&self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &mut System) {
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
let current_order = backend.get_current_order();
match backend.get_mode() {
UIMode::Run(ref run) => {
let run_enabled = run.is_run_enabled(current_order);
if let Some(duration_ratio) = run.wait_for_thread_start_ratio() {
ui.add(egui::ProgressBar::new(duration_ratio as f32));
} else if ui.add_enabled(run_enabled, egui::Button::new("run"))
.on_hover_text(strings::TTIP_APPLY_RUN)
.clicked_or_shortcut(ui, self.shortcuts.apply)
{
backend.run_shell_command_with_process_affinity(system);
}
},
UIMode::View(..) => {},
UIMode::Edit(ref edit) => {
let apply_enabled = edit.is_apply_enabled();
if ui.add_enabled(apply_enabled, egui::Button::new("apply"))
.on_hover_text(strings::TTIP_APPLY)
.clicked_or_shortcut(ui, self.shortcuts.apply )
{
backend.edit_apply_clicked(system);
}
},
}
});
}
fn topology_header(&self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
ui.horizontal(|ui|{
match backend.get_mode_mut() {
UIMode::Run(ref mut run) => {
let heading = ui.label(strings::run()).on_hover_text(strings::TTIP_RUN);
let run_edit = ui.add(egui::TextEdit::singleline(run.get_command_mut()).desired_width(180.0))
.labelled_by(heading.id)
.focus_on_shortcut(ui, self.shortcuts.run);
if self.first_frame {
run_edit.request_focus();
}
let subprocess_label = ui.label(strings::subprocess())
.on_hover_text(strings::TTIP_SUBPROCESS);
ui.add(egui::TextEdit::singleline(run.get_subprocess_pattern_mut()).desired_width(240.0))
.labelled_by(subprocess_label.id)
.focus_on_shortcut(ui, self.shortcuts.subprocess);
},
UIMode::View(..) => {ui.heading("VIEW").on_hover_text(strings::TTIP_VIEW);},
UIMode::Edit(..) => {ui.heading("EDIT").on_hover_text(strings::TTIP_EDIT);},
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
ui.style_mut().spacing.item_spacing = egui::vec2(1.0, 0.0);
ui.style_mut().override_text_style = Some(egui::TextStyle::Heading);
match backend.get_mode() {
UIMode::Run(..) => {},
UIMode::View(ref view) => {
if ui.add(egui::Button::new("EDIT").rounding(egui::Rounding::none()))
.on_hover_text(strings::TTIP_EDIT_SWITCH)
.clicked_or_shortcut(ui, self.shortcuts.mode_edit)
{
backend.set_next_mode_edit(view.get_selected_process(), view.get_process_affinity().clone(), None);
}
if ui.add(egui::Button::new("RUN").rounding(egui::Rounding::none()))
.on_hover_text(strings::TTIP_RUN_SWITCH)
.clicked_or_shortcut(ui, self.shortcuts.mode_run)
{
backend.set_next_mode_run(system);
}
},
UIMode::Edit(ref edit) => {
if ui.add(egui::Button::new("VIEW").rounding(egui::Rounding::none()))
.on_hover_text(strings::TTIP_VIEW_SWITCH)
.clicked_or_shortcut(ui, self.shortcuts.mode_view)
{
backend.set_next_mode_view(system, edit.get_selected_process());
}
if ui.add(egui::Button::new("RUN").rounding(egui::Rounding::none()))
.on_hover_text(strings::TTIP_RUN_SWITCH)
.clicked_or_shortcut(ui, self.shortcuts.mode_run)
{
backend.set_next_mode_run(system);
}
},
}
});
});
ui.separator();
}
fn topology_buttons_process(&self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
ui.horizontal(|ui|{
if ui.button(strings::no_smt()).on_hover_text(strings::TTIP_NO_SMT).clicked_or_shortcut(ui, self.shortcuts.no_smt) {
backend.disable_affinity_smt(system);
}
let current_order = backend.get_current_order();
if let UIMode::Run(ref mut run) = backend.get_mode_mut() {
if run.have_thread_affinities(current_order) {
ui.label(strings::apply_thread_affinities())
.on_hover_text(strings::TTIP_APPLY_THREAD_AFFINITIES);
ui.add(egui::DragValue::new(run.get_thread_apply_time_mut()).clamp_range(0.0 ..= 600.0).fixed_decimals(2).suffix("s").speed(0.01))
.on_hover_text(strings::TTIP_APPLY_THREAD_AFFINITIES)
.focus_on_shortcut(ui, self.shortcuts.thread_apply_time);
}
}
});
}
fn topology_side_buttons(&self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &mut System) {
ui.vertical(|ui|{
ui.strong(strings::THREAD_ORDER).on_hover_text(strings::TTIP_ORDER);
let shortcut_pressed = ui.checkbox(backend.get_thread_order_ignore_main_thread_mut(), strings::IGNORE_MAIN)
.on_hover_text(strings::TTIP_IGNORE_MAIN)
.shortcut(ui, self.shortcuts.order_ignore_main);
if shortcut_pressed {
*backend.get_thread_order_ignore_main_thread_mut() = !backend.get_thread_order_ignore_main_thread();
}
let ignore_main_thread = backend.get_thread_order_ignore_main_thread();
let mut current_order = backend.get_current_order();
match backend.get_mode_mut() {
UIMode::Run(ref mut run) => {
if Self::thread_order_radios(ui, &self.shortcuts, &mut current_order) {
run.apply_thread_order(¤t_order.unwrap(), system, ignore_main_thread);
}
},
UIMode::View(ref view) => {
current_order = Self::thread_order_buttons(ui, &self.shortcuts);
if let Some(order) = current_order {
let (selected_cores_threads, selected_cores_process) = view.apply_thread_order(order, system, ignore_main_thread);
let selected_process = view.get_selected_process();
backend.set_next_mode_edit_hashmap(selected_process, selected_cores_process, selected_cores_threads);
}
},
UIMode::Edit(ref mut edit) => {
current_order = Self::thread_order_buttons(ui, &self.shortcuts);
if let Some(order) = current_order {
edit.apply_thread_order(order, system, ignore_main_thread);
}
}
}
*backend.get_current_order_mut() = current_order;
});
}
fn thread_order_radios(ui: &mut egui::Ui, shortcuts: &shortcuts::Shortcuts, order: &mut Option<ThreadOrder>) -> bool {
if ui.radio_value(order, Some(ThreadOrder::ByLCore), "HW threads").on_hover_text(strings::TTIP_ORDER_PU).clicked_or_shortcut(ui, shortcuts.order_pu) {
} else if ui.radio_value(order, Some(ThreadOrder::ByCore), "cores").on_hover_text(strings::TTIP_ORDER_CORES).clicked_or_shortcut(ui, shortcuts.order_cores) {
} else if ui.radio_value(order, Some(ThreadOrder::ByL3), "L3").on_hover_text(strings::TTIP_ORDER_L3).clicked_or_shortcut(ui, shortcuts.order_l3) {
} else if ui.radio_value(order, None, "none").on_hover_text(strings::TTIP_ORDER_NONE).clicked_or_shortcut(ui, shortcuts.order_none) {
return false
} else {
return false
}
true
}
fn thread_order_buttons(ui: &mut egui::Ui, shortcuts: &shortcuts::Shortcuts) -> Option<ThreadOrder> {
if ui.button("HW threads").on_hover_text(strings::TTIP_ORDER_PU).clicked_or_shortcut(ui, shortcuts.order_pu) {
Some(ThreadOrder::ByLCore)
} else if ui.button("cores").on_hover_text(strings::TTIP_ORDER_CORES).clicked_or_shortcut(ui, shortcuts.order_cores) {
Some(ThreadOrder::ByCore)
} else if ui.button("L3").on_hover_text(strings::TTIP_ORDER_L3).clicked_or_shortcut(ui, shortcuts.order_l3) {
Some(ThreadOrder::ByL3)
} else {
None
}
}
fn topology_grid(&mut self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
ui.visuals_mut().override_text_color = Some(egui::Color32::BLACK);
self.topology_object(backend, ui, system, system.cache().root());
ui.visuals_mut().override_text_color = None;
}
fn topology_object_draw_children(&mut self,
backend: &mut UIBackend,
ui: &mut egui::Ui,
children: TopologyObjectChildren,
system: &System,
object_type: &hwloc2::ObjectType,
horizontal: bool)
{
let old_spacing = ui.spacing().item_spacing;
let first_type = children.peek().unwrap().object_type.clone();
if *object_type == hwloc2::ObjectType::Core {
ui.spacing_mut().item_spacing.x = 2.0;
} else if children.clone().all(|child| child.object_type == first_type) {
ui.spacing_mut().item_spacing = egui::vec2(0.0, 0.0);
}
let (rows, cols) = util::squarish_grid_dimensions(children.len(), horizontal);
ui.vertical(|ui|{
for row in 0..rows {
ui.horizontal(|ui|{
for col in 0..cols {
self.topology_object(backend, ui, system, children.get(row * cols + col));
}
});
}
});
ui.spacing_mut().item_spacing = old_spacing;
}
fn topology_object(
&mut self,
backend: &mut UIBackend,
ui: &mut egui::Ui,
system: &System,
obj: &TopologyObjectInfo)
{
if obj.object_type == hwloc2::ObjectType::PU {
self.topology_object_pu(backend, ui, system, obj);
return;
}
let draw_label = |ui: &mut egui::Ui, first_group, thread_rendering: bool| {
if first_group && !thread_rendering {
Self::topology_object_label(ui, obj);
}
};
let mut draw_children_auto_frame = |ui: &mut egui::Ui, children, first_group| {
if !obj.use_frame {
draw_label(ui, first_group, backend.get_mode().currently_rendering_a_thread());
self.topology_object_draw_children(backend, ui, children, system, &obj.object_type, obj.use_horizontal);
return;
}
let (frame, is_hovered) = self.object_frame(backend, obj, ui.style());
if is_hovered {
self.last_hovered_frame_object = None;
}
let response = frame.show(ui, |ui|{
draw_label(ui, first_group, backend.get_mode().currently_rendering_a_thread());
self.topology_object_draw_children(backend, ui, children, system, &obj.object_type, obj.use_horizontal);
}).response.interact(egui::Sense::union(egui::Sense::hover(), egui::Sense::click()));
let hover_depth = self.last_hovered_frame_object.as_ref().map_or_else(||0, |(id, ..)| system.cache().get_by_id(id.clone()).depth);
if response.hovered() && obj.depth > hover_depth {
self.last_hovered_frame_object =
Some((obj.id(), backend.get_mode().currently_rendering_thread_id()));
}
if response.clicked() {
backend.handle_group_click(system, obj);
}
};
let children_iter_base = system.cache().get_children(obj);
ui.vertical(|ui| {
let mut first_group = true;
let children_iter = children_iter_base.clone();
children_iter.by_group(|children, last|{
if obj.use_horizontal {
ui.horizontal(|ui| { draw_children_auto_frame(ui, children, first_group); });
} else {
draw_children_auto_frame(ui, children, first_group);
}
first_group = false;
if !last {
ui.add_space(4.0);
}
});
});
}
fn topology_object_pu(&self,
backend: &mut UIBackend,
ui: &mut egui::Ui,
system: &System,
obj: &TopologyObjectInfo)
{
let pu_id = obj.os_index;
let core_color = self.topology_pu_color(backend, pu_id);
ui.spacing_mut().button_padding = egui::vec2(2.0, 1.0);
let response = if backend.get_mode().currently_rendering_a_thread() {
ui.spacing_mut().interact_size.y = 0.0;
ui.style_mut().override_text_style = Some(egui::TextStyle::Name("ThreadView".into()));
ui.add(egui::Button::new(format!("{:0>2}", pu_id)).fill(core_color).rounding(egui::Rounding::none()))
} else {
ui.add(egui::Button::new(format!("{:0>2}", pu_id)).fill(core_color).small().rounding(egui::Rounding::none()))
};
if response.clicked() {
backend.handle_group_click(system, obj);
}
ui.style_mut().override_text_style = None;
}
fn topology_pu_color(&self, backend: &mut UIBackend, pu_id: u32) -> egui::Color32 {
let colors = (egui::Color32::WHITE, egui::Color32::GREEN, egui::Color32::YELLOW, egui::Color32::from_rgb(255,196,0));
backend.pu_color(pu_id, colors)
}
fn topology_object_label(ui: &mut egui::Ui, obj: &TopologyObjectInfo) {
if let Some(label) = &obj.label {
match &label.style_override {
Some(TopologyObjectLabelStyle::SmallCache) => {
ui.style_mut().override_text_style = Some(egui::TextStyle::Name("SmallCache".into()));
},
None => {}
}
if obj.use_horizontal && label.lines.len() > 1 {
ui.vertical(|ui|{
for line in label.lines.iter() {
ui.label(line);
}
});
ui.add_space(4.0);
}
else if label.lines.len() > 1 {
ui.label(label.lines.join(" "));
} else {
ui.label(&label.lines[0]);
}
ui.style_mut().override_text_style = None;
}
}
fn object_frame(&self, backend: &UIBackend, obj: &TopologyObjectInfo, base_style: &egui::Style) -> (egui::Frame, bool) {
let thread = backend.get_mode().currently_rendering_a_thread();
let mut frame = egui::Frame::group(base_style);
frame.inner_margin = match (thread, obj.use_horizontal, obj.is_single_core) {
(false, _, _) => egui::Margin::same(4.0),
(true, true, _) => egui::Margin::symmetric(6.0, 3.0),
(true, false, false) => egui::Margin{ left: 3.0, right: 3.0, top: 6.0, bottom: 3.0 },
(true, false, true) => egui::Margin{ left: 1.0, right: 1.0, top: 6.0, bottom: 0.0 },
};
frame.stroke.color = egui::Color32::from_rgb(16,16, 16);
frame.rounding = egui::Rounding::none();
let is_hovered = self.last_hovered_frame_object ==
Some((obj.id(), backend.get_mode().currently_rendering_thread_id()));
let (fill, hover) = if is_hovered {
(egui::Color32::from_rgb(24, 160, 24), true)
} else {
(egui::Color32::from_rgb(192, 192, 192), false)
};
frame.fill = fill;
(frame, hover)
}
fn topology_threads(&mut self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
let default_open = false;
let id = ui.make_persistent_id("threads");
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, default_open)
.show_header(ui, |ui| {
self.topology_threads_header(backend, ui, system);
})
.body(|ui| {
match backend.get_mode() {
UIMode::Run(..) => {
self.topology_threads_main_run(backend, ui, system);
},
UIMode::View(ref view) => {
self.topology_threads_main(backend, ui, system, view.get_selected_process());
},
UIMode::Edit(ref edit) => {
self.topology_threads_main(backend, ui, system, edit.get_selected_process());
}
}
});
}
fn topology_threads_header(&self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
ui.horizontal(|ui|{
ui.strong(strings::THREADS).on_hover_text(strings::TTIP_THREADS);
let thread_order_ignore_main_thread = backend.get_thread_order_ignore_main_thread();
let current_order = *backend.get_current_order_mut();
let mut thread_count = backend.get_thread_count();
let count_prefix = format!("{}: ", strings::THREADS_COUNT);
match backend.get_mode_mut() {
UIMode::Run(ref mut run) => {
ui.add(egui::DragValue::new(&mut thread_count)
.prefix(count_prefix)
.clamp_range(0 ..=1024))
.on_hover_text(strings::TTIP_THREADS_COUNT_RUN)
.focus_on_shortcut(ui, self.shortcuts.thread_count);
if ui.button("clear").on_hover_text(strings::TTIP_THREADS_CLEAR).clicked_or_shortcut(ui, self.shortcuts.clear_threads) {
run.clear_threads();
}
backend.run_update_thread_count(thread_count, system);
},
UIMode::View(ref view) => {
ui.add_enabled(false, egui::DragValue::new(&mut thread_count).prefix(count_prefix))
.on_hover_text(strings::TTIP_THREADS_COUNT);
},
UIMode::Edit(ref mut edit) => {
ui.add_enabled(false, egui::DragValue::new(&mut thread_count).prefix(count_prefix))
.on_hover_text(strings::TTIP_THREADS_COUNT);
if ui.button(strings::CLEAR).on_hover_text(strings::TTIP_THREADS_CLEAR).clicked_or_shortcut(ui, self.shortcuts.clear_threads) {
edit.clear_threads();
}
}
}
});
}
fn thread_cols(ui: &egui::Ui, system: &System) -> usize {
(ui.available_size().x / (29.5 * (system.logical_core_count() as f32).sqrt())).trunc() as usize
}
fn topology_threads_main_run(&mut self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System) {
let cols = Self::thread_cols(ui, system);
let thread_count = backend.get_thread_count();
let mut start_thread_index = 0;
while start_thread_index < thread_count {
ui.horizontal(|ui| {
for thread_index in start_thread_index..usize::min(start_thread_index + cols, thread_count) {
let name = if thread_index > 0 {format!("{}", thread_index)} else {String::from("[main]")};
ui.vertical(|ui|{
let UIMode::Run(ref run) = backend.get_mode_mut() else {
panic!("this must be called only in Run mode");
};
let edited = backend.get_mode().is_thread_edited(thread_index.try_into().unwrap());
self.thread_name(ui, name.as_str(), edited);
backend.get_mode_mut().set_currently_rendered_thread(Some(thread_index.try_into().unwrap()));
self.topology_grid(backend, ui, system);
backend.get_mode_mut().set_currently_rendered_thread(None);
});
}
start_thread_index += cols;
});
}
}
fn thread_name(&self, ui: &mut egui::Ui, name: &str, edited: bool) {
ui.style_mut().override_text_style = Some(egui::TextStyle::Name("ThreadView".into()));
ui.horizontal(|ui|{
ui.add(egui::Label::new(util::truncate_and_add_ellipsis(name, 12)).wrap(false)).on_hover_text(name);
if edited {
ui.colored_label(egui::Color32::YELLOW, "*");
}
});
ui.style_mut().override_text_style = None;
}
fn topology_threads_main(&mut self, backend: &mut UIBackend, ui: &mut egui::Ui, system: &System, pid: sysinfo::Pid) {
let threads = system.get_nonmain_threads_of(pid);
let cols = Self::thread_cols(ui, system);
let mut all_threads_iter = std::iter::once((pid, "[main]")).chain(threads.into_iter().flatten().map(|(tid, thread)| (tid, thread.name()))).peekable();
while all_threads_iter.peek().is_some() {
ui.horizontal(|ui| {
for (tid, name) in all_threads_iter.by_ref().take(cols) {
ui.vertical(|ui|{
let edited = backend.get_mode().is_thread_edited(tid.as_u32());
self.thread_name(ui, name, edited);
backend.get_mode_mut().set_currently_rendered_thread(Some(tid.as_u32()));
self.topology_grid(backend, ui, system);
backend.get_mode_mut().set_currently_rendered_thread(None);
});
}
});
}
}
}