MCP-Relational-Data/src/gui.rs

579 lines
26 KiB
Rust
Executable File

//! GUI mode — iced 0.12 desktop application.
//!
//! Two-column layout with 8 calculator panels.
use crate::calc::*;
use iced::widget::{button, column, container, row, text, text_input, scrollable};
use iced::{
alignment, Application, Command, Element, Length, Settings, Theme,
};
// ══════════════════════════════════════════════════════════════════════
// Input parsing helper
// ══════════════════════════════════════════════════════════════════════
/// Parse a trimmed input string to f64 with a default fallback.
fn parse_f64(raw: &str, fallback: f64) -> f64 {
raw.trim().parse().unwrap_or(fallback)
}
// ══════════════════════════════════════════════════════════════════════
// Per-calculator state
// ══════════════════════════════════════════════════════════════════════
struct OhmState {
p_in: String, i_in: String, r_in: String, v_in: String,
p_out: String, i_out: String, r_out: String, v_out: String,
}
struct MarginState {
cost_in: String, sell_in: String,
profit_out: String, margin_pct_out: String, markup_pct_out: String,
}
struct RoiState {
investment_in: String, revenue_in: String,
profit_out: String, roi_pct_out: String,
}
struct TokenState {
model: AiModel,
input_tok_in: String, output_tok_in: String,
input_cost_out: String, output_cost_out: String, total_cost_out: String,
}
struct ElecState {
power_in: String, hours_in: String, days_in: String, rate_in: String,
daily_kwh_out: String, monthly_kwh_out: String,
monthly_cost_out: String, yearly_cost_out: String,
}
struct BeState {
fixed_in: String, price_in: String, variable_in: String,
units_out: String, revenue_out: String,
}
struct SolarState {
panels_in: String, panel_watts_in: String, sun_hours_in: String,
rate_in: String, system_cost_in: String, efficiency_in: String,
system_kw_out: String, daily_kwh_out: String, monthly_kwh_out: String,
yearly_kwh_out: String, monthly_rev_out: String, yearly_rev_out: String,
payback_out: String, lifetime_out: String,
}
struct Print3dState {
fil_cost_in: String, weight_in: String, time_in: String,
printer_w_in: String, elec_rate_in: String, fail_rate_in: String,
sell_price_in: String,
fil_kg_out: String, mat_cost_out: String, energy_cost_out: String,
cost_per_out: String, profit_out: String, margin_out: String,
}
// ══════════════════════════════════════════════════════════════════════
// Main application state
// ══════════════════════════════════════════════════════════════════════
struct App {
ohm: OhmState,
margin: MarginState,
roi: RoiState,
token: TokenState,
elec: ElecState,
be: BeState,
solar: SolarState,
print3d: Print3dState,
}
// ══════════════════════════════════════════════════════════════════════
// Messages
// ══════════════════════════════════════════════════════════════════════
#[derive(Debug, Clone)]
enum Message {
OhmPower(String), OhmCurrent(String), OhmResistance(String), OhmVoltage(String),
MarginCost(String), MarginSell(String),
RoiInvestment(String), RoiRevenue(String),
TokenPrev, TokenNext, TokenInputTok(String), TokenOutputTok(String),
ElecPower(String), ElecHours(String), ElecDays(String), ElecRate(String),
BeFixed(String), BePrice(String), BeVariable(String),
SolarPanels(String), SolarPanelW(String), SolarSunHrs(String),
SolarRate(String), SolarSysCost(String), SolarEfficiency(String),
P3dFilCost(String), P3dWeight(String), P3dTime(String),
P3dPrinterW(String), P3dElecRate(String), P3dFailRate(String), P3dSellPrice(String),
}
// ══════════════════════════════════════════════════════════════════════
// Application
// ══════════════════════════════════════════════════════════════════════
pub fn run() -> iced::Result {
App::run(Settings::default())
}
impl Application for App {
type Executor = iced::executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_: Self::Flags) -> (Self, Command<Self::Message>) {
(
Self {
ohm: OhmState {
p_in: String::new(), i_in: String::new(),
r_in: String::new(), v_in: String::new(),
p_out: "0 Watts(W)".into(), i_out: "0 Amps(A)".into(),
r_out: "0 Ohms(\u{03A9})".into(), v_out: "0 Volts(V)".into(),
},
margin: MarginState {
cost_in: String::new(), sell_in: String::new(),
profit_out: "0".into(), margin_pct_out: "0 %".into(),
markup_pct_out: "0 %".into(),
},
roi: RoiState {
investment_in: String::new(), revenue_in: String::new(),
profit_out: "0".into(), roi_pct_out: "0 %".into(),
},
token: TokenState {
model: AiModel::Gpt4o,
input_tok_in: String::new(), output_tok_in: String::new(),
input_cost_out: "$0.00".into(), output_cost_out: "$0.00".into(),
total_cost_out: "$0.00".into(),
},
elec: ElecState {
power_in: String::new(), hours_in: String::new(),
days_in: String::new(), rate_in: String::new(),
daily_kwh_out: "0 kWh".into(), monthly_kwh_out: "0 kWh".into(),
monthly_cost_out: "$0.00".into(), yearly_cost_out: "$0.00".into(),
},
be: BeState {
fixed_in: String::new(), price_in: String::new(),
variable_in: String::new(),
units_out: "0".into(), revenue_out: "$0.00".into(),
},
solar: SolarState {
panels_in: String::new(), panel_watts_in: String::new(),
sun_hours_in: String::new(), rate_in: String::new(),
system_cost_in: String::new(), efficiency_in: String::new(),
system_kw_out: "0 kW".into(), daily_kwh_out: "0 kWh".into(),
monthly_kwh_out: "0 kWh".into(), yearly_kwh_out: "0 kWh".into(),
monthly_rev_out: "$0.00".into(), yearly_rev_out: "$0.00".into(),
payback_out: "0 years".into(), lifetime_out: "$0.00".into(),
},
print3d: Print3dState {
fil_cost_in: String::new(), weight_in: String::new(),
time_in: String::new(), printer_w_in: String::new(),
elec_rate_in: String::new(), fail_rate_in: String::new(),
sell_price_in: String::new(),
fil_kg_out: "0 kg".into(), mat_cost_out: "$0.00".into(),
energy_cost_out: "$0.00".into(), cost_per_out: "$0.00".into(),
profit_out: "$0.00".into(), margin_out: "0 %".into(),
},
},
Command::none(),
)
}
fn title(&self) -> String { "MCP-Relational-Data".into() }
fn update(&mut self, msg: Self::Message) -> Command<Self::Message> {
match msg {
Message::OhmPower(s) => { self.ohm.p_in = s; self.ohm.recalculate(); }
Message::OhmCurrent(s) => { self.ohm.i_in = s; self.ohm.recalculate(); }
Message::OhmResistance(s) => { self.ohm.r_in = s; self.ohm.recalculate(); }
Message::OhmVoltage(s) => { self.ohm.v_in = s; self.ohm.recalculate(); }
Message::MarginCost(s) => { self.margin.cost_in = s; self.margin.recalculate(); }
Message::MarginSell(s) => { self.margin.sell_in = s; self.margin.recalculate(); }
Message::RoiInvestment(s) => { self.roi.investment_in = s; self.roi.recalculate(); }
Message::RoiRevenue(s) => { self.roi.revenue_in = s; self.roi.recalculate(); }
Message::TokenPrev => { self.token.model = prev_model(self.token.model); self.token.recalculate(); }
Message::TokenNext => { self.token.model = next_model(self.token.model); self.token.recalculate(); }
Message::TokenInputTok(s) => { self.token.input_tok_in = s; self.token.recalculate(); }
Message::TokenOutputTok(s) => { self.token.output_tok_in = s; self.token.recalculate(); }
Message::ElecPower(s) => { self.elec.power_in = s; self.elec.recalculate(); }
Message::ElecHours(s) => { self.elec.hours_in = s; self.elec.recalculate(); }
Message::ElecDays(s) => { self.elec.days_in = s; self.elec.recalculate(); }
Message::ElecRate(s) => { self.elec.rate_in = s; self.elec.recalculate(); }
Message::BeFixed(s) => { self.be.fixed_in = s; self.be.recalculate(); }
Message::BePrice(s) => { self.be.price_in = s; self.be.recalculate(); }
Message::BeVariable(s) => { self.be.variable_in = s; self.be.recalculate(); }
Message::SolarPanels(s) => { self.solar.panels_in = s; self.solar.recalculate(); }
Message::SolarPanelW(s) => { self.solar.panel_watts_in = s; self.solar.recalculate(); }
Message::SolarSunHrs(s) => { self.solar.sun_hours_in = s; self.solar.recalculate(); }
Message::SolarRate(s) => { self.solar.rate_in = s; self.solar.recalculate(); }
Message::SolarSysCost(s) => { self.solar.system_cost_in = s; self.solar.recalculate(); }
Message::SolarEfficiency(s) => { self.solar.efficiency_in = s; self.solar.recalculate(); }
Message::P3dFilCost(s) => { self.print3d.fil_cost_in = s; self.print3d.recalculate(); }
Message::P3dWeight(s) => { self.print3d.weight_in = s; self.print3d.recalculate(); }
Message::P3dTime(s) => { self.print3d.time_in = s; self.print3d.recalculate(); }
Message::P3dPrinterW(s) => { self.print3d.printer_w_in = s; self.print3d.recalculate(); }
Message::P3dElecRate(s) => { self.print3d.elec_rate_in = s; self.print3d.recalculate(); }
Message::P3dFailRate(s) => { self.print3d.fail_rate_in = s; self.print3d.recalculate(); }
Message::P3dSellPrice(s) => { self.print3d.sell_price_in = s; self.print3d.recalculate(); }
}
Command::none()
}
fn view(&self) -> Element<Self::Message> {
let left = column![
ohm_section(&self.ohm),
elec_section(&self.elec),
solar_section(&self.solar),
]
.spacing(16);
let right = column![
margin_section(&self.margin),
roi_section(&self.roi),
token_section(&self.token),
be_section(&self.be),
print3d_section(&self.print3d),
]
.spacing(16);
let body = row![left, right]
.spacing(16)
.padding(16);
container(scrollable(body).height(Length::Fill))
.width(Length::Fill)
.height(Length::Fill)
.padding([8, 16])
.into()
}
}
// ══════════════════════════════════════════════════════════════════════
// Section views
// ══════════════════════════════════════════════════════════════════════
fn ohm_section<'a>(s: &'a OhmState) -> Element<'a, Message> {
container(
column![
text("Ohm's Law & Watts Law").size(15),
text("Enter any 2 of 4 values to solve for the remaining 2. \
Applies to circuit design, audio impedance matching, \
MOSFET PSU design, and LED driver sizing.")
.size(10),
q_block("Power (W)", &s.p_in, &s.p_out, Message::OhmPower),
q_block("Current (A)", &s.i_in, &s.i_out, Message::OhmCurrent),
q_block("Resistance (\u{03A9})", &s.r_in, &s.r_out, Message::OhmResistance),
q_block("Voltage (V)", &s.v_in, &s.v_out, Message::OhmVoltage),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn margin_section<'a>(s: &'a MarginState) -> Element<'a, Message> {
container(
column![
text("Margin & Markup").size(15),
text("Ecommerce pricing: calculate profit, margin %, and markup % \
from cost and selling price.")
.size(10),
q_block("Cost Price ($)", &s.cost_in, &s.profit_out, Message::MarginCost),
q_block("Selling Price ($)", &s.sell_in, &s.margin_pct_out, Message::MarginSell),
io_row("Markup", &s.markup_pct_out),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn roi_section<'a>(s: &'a RoiState) -> Element<'a, Message> {
container(
column![
text("Return on Investment (ROI)").size(15),
text("Measure profitability of ad spend, marketing campaigns, \
and capital expenditures.")
.size(10),
q_block("Investment ($)", &s.investment_in, &s.profit_out, Message::RoiInvestment),
q_block("Revenue ($)", &s.revenue_in, &s.roi_pct_out, Message::RoiRevenue),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn token_section<'a>(s: &'a TokenState) -> Element<'a, Message> {
container(
column![
text("AI Token Cost Estimator").size(15),
text("Compare per-million-token pricing across 6 major LLMs \
(GPT-4o, Claude 3.5 Sonnet, etc.).")
.size(10),
row![
button(text("\u{25C0}")).on_press(Message::TokenPrev),
text(s.model.label()).size(12),
button(text("\u{25B6}")).on_press(Message::TokenNext),
]
.spacing(8)
.align_items(alignment::Alignment::Center),
q_block("Input Tokens", &s.input_tok_in, &s.input_cost_out, Message::TokenInputTok),
q_block("Output Tokens", &s.output_tok_in, &s.output_cost_out, Message::TokenOutputTok),
io_row("Total Cost", &s.total_cost_out),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn elec_section<'a>(s: &'a ElecState) -> Element<'a, Message> {
container(
column![
text("Electricity Cost").size(15),
text("Project monthly and yearly power costs for GPU servers, \
datacenter racks, and mining rigs.")
.size(10),
q_block("Power (W)", &s.power_in, &s.daily_kwh_out, Message::ElecPower),
q_block("Hours / Day", &s.hours_in, &s.monthly_kwh_out, Message::ElecHours),
q_block("Days / Month", &s.days_in, &s.monthly_cost_out, Message::ElecDays),
q_block("Rate ($/kWh)", &s.rate_in, &s.yearly_cost_out, Message::ElecRate),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn be_section<'a>(s: &'a BeState) -> Element<'a, Message> {
container(
column![
text("Break-Even Analysis").size(15),
text("How many units must you sell to cover all fixed and variable costs?")
.size(10),
q_block("Fixed Costs ($)", &s.fixed_in, &s.units_out, Message::BeFixed),
q_block("Price per Unit ($)", &s.price_in, &s.revenue_out, Message::BePrice),
q_block("Variable Cost / Unit ($)", &s.variable_in, "", Message::BeVariable),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn solar_section<'a>(s: &'a SolarState) -> Element<'a, Message> {
container(
column![
text("Solar Panel Revenue").size(15),
text("Estimate generation, revenue, payback period, and 25-year \
lifetime earnings for a residential or commercial solar array.")
.size(10),
q_block("# Panels", &s.panels_in, &s.system_kw_out, Message::SolarPanels),
q_block("Watts / Panel", &s.panel_watts_in,&s.daily_kwh_out, Message::SolarPanelW),
q_block("Sun Hours / Day",&s.sun_hours_in, &s.monthly_kwh_out, Message::SolarSunHrs),
q_block("Sell-Back Rate ($/kWh)", &s.rate_in, &s.yearly_kwh_out, Message::SolarRate),
q_block("System Cost ($)", &s.system_cost_in, &s.monthly_rev_out, Message::SolarSysCost),
q_block("Efficiency (%)", &s.efficiency_in, &s.yearly_rev_out, Message::SolarEfficiency),
io_row("Payback Period", &s.payback_out),
io_row("25-Year Revenue", &s.lifetime_out),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
fn print3d_section<'a>(s: &'a Print3dState) -> Element<'a, Message> {
container(
column![
text("3D Print Cost Analysis").size(15),
text("Calculate per-print cost including filament, electricity, \
and failure rate. See profit and margin when selling prints.")
.size(10),
q_block("Filament Cost ($/kg)", &s.fil_cost_in, &s.fil_kg_out, Message::P3dFilCost),
q_block("Print Weight (g)", &s.weight_in, &s.mat_cost_out, Message::P3dWeight),
q_block("Print Time (h)", &s.time_in, &s.energy_cost_out,Message::P3dTime),
q_block("Printer Watts", &s.printer_w_in, &s.cost_per_out, Message::P3dPrinterW),
q_block("Electricity Rate ($/kWh)", &s.elec_rate_in, &s.profit_out, Message::P3dElecRate),
q_block("Failure Rate (%)", &s.fail_rate_in, &s.margin_out, Message::P3dFailRate),
q_block("Sell Price ($)", &s.sell_price_in, "", Message::P3dSellPrice),
]
.spacing(5),
)
.padding(12)
.width(Length::Fill)
.into()
}
// ══════════════════════════════════════════════════════════════════════
// Shared UI helpers
// ══════════════════════════════════════════════════════════════════════
fn q_block<'a>(
label: &'a str,
input_val: &'a str,
result_val: &'a str,
msg: fn(String) -> Message,
) -> Element<'a, Message> {
column![
text(label).size(11),
row![
text_input("", input_val)
.on_input(msg)
.width(Length::FillPortion(3))
.padding(4),
text(result_val)
.width(Length::FillPortion(4))
.vertical_alignment(alignment::Vertical::Center),
]
.spacing(6)
.align_items(alignment::Alignment::Center),
]
.spacing(2)
.into()
}
fn io_row<'a>(label: &'a str, value: &'a str) -> Element<'a, Message> {
row![
text(label).size(11).width(Length::FillPortion(3)),
text(value)
.size(11)
.width(Length::FillPortion(4))
.vertical_alignment(alignment::Vertical::Center),
]
.spacing(6)
.align_items(alignment::Alignment::Center)
.into()
}
// ══════════════════════════════════════════════════════════════════════
// Recalculate impls (delegate to calc.rs)
// ══════════════════════════════════════════════════════════════════════
impl OhmState {
fn recalculate(&mut self) {
let p = parse_f64(&self.p_in, 0.0);
let i = parse_f64(&self.i_in, 0.0);
let r = parse_f64(&self.r_in, 0.0);
let v = parse_f64(&self.v_in, 0.0);
// Reject negative power or resistance — physically invalid.
if (self.p_in.trim().parse::<f64>().is_ok() && p < 0.0)
|| (self.r_in.trim().parse::<f64>().is_ok() && r < 0.0)
{
self.p_out = "0 Watts(W)".into();
self.i_out = "0 Amps(A)".into();
self.r_out = "0 Ohms(\u{03A9})".into();
self.v_out = "0 Volts(V)".into();
return;
}
let (p, i, r, v) = ohm_calculate(p, i, r, v);
self.p_out = fmt_eng(p, "Watts(W)");
self.i_out = fmt_eng(i, "Amps(A)");
self.r_out = fmt_eng(r, "Ohms(\u{03A9})");
self.v_out = fmt_eng(v, "Volts(V)");
}
}
impl MarginState {
fn recalculate(&mut self) {
let cost = parse_f64(&self.cost_in, 0.0);
let sell = parse_f64(&self.sell_in, 0.0);
let (profit, margin, markup) = margin_calculate(cost, sell);
self.profit_out = fmt_num(profit);
self.margin_pct_out = fmt_pct(margin);
self.markup_pct_out = fmt_pct(markup);
}
}
impl RoiState {
fn recalculate(&mut self) {
let inv = parse_f64(&self.investment_in, 0.0);
let rev = parse_f64(&self.revenue_in, 0.0);
let (profit, roi) = roi_calculate(inv, rev);
self.profit_out = fmt_num(profit);
self.roi_pct_out = fmt_pct(roi);
}
}
impl TokenState {
fn recalculate(&mut self) {
let in_tok = parse_f64(&self.input_tok_in, 0.0);
let out_tok = parse_f64(&self.output_tok_in, 0.0);
let (in_cost, out_cost, total) = token_cost_calculate(self.model, in_tok, out_tok);
self.input_cost_out = fmt_dollar(in_cost);
self.output_cost_out = fmt_dollar(out_cost);
self.total_cost_out = fmt_dollar(total);
}
}
impl ElecState {
fn recalculate(&mut self) {
let watts = parse_f64(&self.power_in, 0.0);
let hours = parse_f64(&self.hours_in, 0.0);
let days = parse_f64(&self.days_in, 30.0);
let rate = parse_f64(&self.rate_in, 0.0);
let (daily, monthly_kwh, monthly_cost, yearly) =
electricity_calculate(watts, hours, days, rate);
self.daily_kwh_out = fmt_eng(daily, "kWh");
self.monthly_kwh_out = fmt_eng(monthly_kwh, "kWh");
self.monthly_cost_out = fmt_dollar(monthly_cost);
self.yearly_cost_out = fmt_dollar(yearly);
}
}
impl BeState {
fn recalculate(&mut self) {
let fixed = parse_f64(&self.fixed_in, 0.0);
let price = parse_f64(&self.price_in, 0.0);
let variable = parse_f64(&self.variable_in, 0.0);
let (units, revenue) = break_even_calculate(fixed, price, variable);
self.units_out = fmt_num(units);
self.revenue_out = fmt_dollar(revenue);
}
}
impl SolarState {
fn recalculate(&mut self) {
let panels = parse_f64(&self.panels_in, 0.0);
let pw = parse_f64(&self.panel_watts_in, 0.0);
let sun = parse_f64(&self.sun_hours_in, 0.0);
let rate = parse_f64(&self.rate_in, 0.0);
let cost = parse_f64(&self.system_cost_in, 0.0);
let eff = parse_f64(&self.efficiency_in, 80.0);
let (kw, daily, monthly, yearly, m_rev, y_rev, payback, lifetime) =
solar_calculate(panels, pw, sun, rate, cost, eff);
self.system_kw_out = fmt_eng(kw, "kW");
self.daily_kwh_out = fmt_eng(daily, "kWh");
self.monthly_kwh_out = fmt_eng(monthly, "kWh");
self.yearly_kwh_out = fmt_eng(yearly, "kWh");
self.monthly_rev_out = fmt_dollar(m_rev);
self.yearly_rev_out = fmt_dollar(y_rev);
self.payback_out = format!("{} years", trim_f(payback, 5));
self.lifetime_out = fmt_dollar(lifetime);
}
}
impl Print3dState {
fn recalculate(&mut self) {
let fc = parse_f64(&self.fil_cost_in, 0.0);
let wt = parse_f64(&self.weight_in, 0.0);
let tm = parse_f64(&self.time_in, 0.0);
let pw = parse_f64(&self.printer_w_in, 0.0);
let er = parse_f64(&self.elec_rate_in, 0.0);
let fr = parse_f64(&self.fail_rate_in, 0.0);
let sp = parse_f64(&self.sell_price_in, 0.0);
let (fkg, mat, nrg, cpp, profit, margin) =
print3d_calculate(fc, wt, tm, pw, er, fr, sp);
self.fil_kg_out = format!("{} kg", trim_f(fkg, 5));
self.mat_cost_out = fmt_dollar(mat);
self.energy_cost_out = fmt_dollar(nrg);
self.cost_per_out = fmt_dollar(cpp);
self.profit_out = fmt_dollar(profit);
self.margin_out = fmt_pct(margin);
}
}