use crate::compat::Vec;
use crate::core::{Color, Rect, Size};
use crate::render::{PaintBackend, RenderContext, SoftwarePaintBackend};
use crate::theme::{global_theme_manager, AppearanceMode};
use crate::widget::{draw_bridge::draw_of, Widget, WidgetFactory};
pub const CENSUS_RECT: Rect = Rect { x: 0, y: 0, width: 240, height: 120 };
pub const CENSUS_TEXT: &str = "Sample";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppearanceCensus {
pub non_background: u32,
pub dominant: Option<(u8, u8, u8)>,
pub detail: u32,
}
impl AppearanceCensus {
pub fn paints_anything(&self) -> bool {
self.non_background > 0
}
pub fn dominant_differs_from(&self, background: Color) -> bool {
match self.dominant {
Some((r, g, b)) => (r, g, b) != (background.r, background.g, background.b),
None => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ControlCensus {
pub name: &'static str,
pub light: AppearanceCensus,
pub dark: AppearanceCensus,
pub light_on_surface: AppearanceCensus,
pub light_background: Color,
pub dark_background: Color,
}
impl ControlCensus {
pub fn differs_between_appearances(&self) -> bool {
self.light.dominant != self.dark.dominant
}
pub fn visible_against_its_surface(&self) -> bool {
self.light_on_surface.dominant_differs_from(self.light_background)
}
}
fn count_ink(frame: &[u8], background: Color) -> AppearanceCensus {
let bg = (background.r, background.g, background.b);
let mut histogram: crate::compat::HashMap<(u8, u8, u8), u32> = crate::compat::HashMap::new();
let mut non_background = 0u32;
for px in frame.chunks_exact(4) {
let (r, g, b, a) = (px[0], px[1], px[2], px[3]);
if a == 0 {
continue;
}
if (r, g, b) != bg {
non_background += 1;
*histogram.entry((r, g, b)).or_insert(0) += 1;
}
}
let dominant = histogram.iter().max_by_key(|(_, count)| **count).map(|(rgb, _)| *rgb);
let dominant_count = dominant.map(|rgb| histogram[&rgb]).unwrap_or(0);
AppearanceCensus {
non_background,
dominant,
detail: non_background.saturating_sub(dominant_count),
}
}
pub const CENSUS_PROBE_BACKGROUND: Color = Color { r: 255, g: 0, b: 255, a: 255 };
fn render_one(widget: &mut dyn Widget, fill: Color) -> AppearanceCensus {
let mut backend = SoftwarePaintBackend::new(
Size { width: CENSUS_RECT.width, height: CENSUS_RECT.height },
1.0,
);
backend.begin_frame(fill);
if let Some(drawable) = draw_of(widget) {
let mut ctx = RenderContext::new(&mut backend);
drawable.draw(&mut ctx);
}
backend.end_frame();
count_ink(backend.frame_rgba(), fill)
}
fn active_background() -> Color {
global_theme_manager()
.current_theme()
.map(|theme| theme.colors.background)
.unwrap_or(Color::WHITE)
}
fn select(appearance: AppearanceMode) {
global_theme_manager().set_appearance(appearance);
}
pub fn install_preset_appearances() {
let mut manager = global_theme_manager();
manager.register_theme(crate::theme::Theme::default());
manager.register_theme(crate::theme::Theme::dark());
}
pub fn census_all_controls() -> Vec<ControlCensus> {
let factory = WidgetFactory::new_with_defaults();
let mut results = Vec::new();
for name in factory.widget_names() {
let mut widget = match factory.create(name, CENSUS_RECT, CENSUS_TEXT) {
Some(widget) => widget,
None => {
log::warn!("rendering census: {name} is registered but could not be constructed");
continue;
}
};
select(AppearanceMode::Light);
let light_background = active_background();
crate::theme::apply_active_theme(&mut *widget);
let light = render_one(&mut *widget, CENSUS_PROBE_BACKGROUND);
let light_on_surface = render_one(&mut *widget, light_background);
select(AppearanceMode::Dark);
let dark_background = active_background();
crate::theme::apply_active_theme(&mut *widget);
let dark = render_one(&mut *widget, CENSUS_PROBE_BACKGROUND);
results.push(ControlCensus {
name,
light,
dark,
light_on_surface,
light_background,
dark_background,
});
}
results
}
pub fn format_row(row: &ControlCensus) -> crate::compat::String {
crate::compat::format!(
"{:<28} {:>8} {:>14} {:>14} {:>7} {:>8}",
row.name,
row.light.non_background,
format_rgb(row.light.dominant),
format_rgb(row.dark.dominant),
if row.differs_between_appearances() { "yes" } else { "NO" },
row.light.detail,
)
}
pub fn format_rgb(rgb: Option<(u8, u8, u8)>) -> crate::compat::String {
match rgb {
Some((r, g, b)) => crate::compat::format!("{r},{g},{b}"),
None => crate::compat::String::from("none"),
}
}