pub struct Gradient { /* private fields */ }Expand description
Precomputed RGB gradient used by Aisling renderers.
Implementations§
Source§impl Gradient
impl Gradient
Sourcepub fn new(stops: impl Into<Vec<Color>>, steps: usize) -> Self
pub fn new(stops: impl Into<Vec<Color>>, steps: usize) -> Self
Examples found in repository?
More examples
examples/story.rs (lines 378-381)
346fn draw_systems(
347 screen: &mut Frame,
348 area: Rect,
349 tasks: &[StoryTask],
350 tick: usize,
351 total_ticks: usize,
352 accent: Color,
353) {
354 draw_panel(screen, area, "FAKE DOWNLOADS // LAB SYSTEMS", accent);
355 let mut y = area.y + 2;
356 let inner_w = area.w.saturating_sub(4).max(1);
357 for task in tasks {
358 if y >= area.y + area.h.saturating_sub(1) {
359 break;
360 }
361 let cycle = total_ticks.max(1) / task.speed.max(1) + 24;
362 let current_tick = (tick + task.offset) % cycle.max(1);
363 let fraction = current_tick as f32 / cycle.max(1) as f32;
364 let current = (task.total as f32 * fraction).round() as u64;
365 let progress = LoaderProgress::from_counts(current.min(task.total), task.total);
366 let rows = task.rows.min(area.y + area.h - y - 1).max(1);
367 let loader = Loader::with_config(
368 task.kind,
369 LoaderConfig::default()
370 .with_size(inner_w, rows)
371 .with_label(task.label)
372 .with_unit(task.unit)
373 .with_fraction(matches!(
374 task.kind,
375 LoaderKind::Tqdm | LoaderKind::Download | LoaderKind::Upload
376 ))
377 .with_gradient(
378 Gradient::new(
379 vec![accent, Color::rgb(74, 224, 255), Color::rgb(255, 186, 74)],
380 24,
381 ),
382 GradientDirection::Horizontal,
383 ),
384 );
385 let frame = loader.frame(tick + task.offset, progress);
386 draw_frame(screen, area.x + 2, y, &frame, inner_w, rows);
387 y += rows + 1;
388 }
389}
390
391fn draw_bottom_bus(
392 screen: &mut Frame,
393 area: Rect,
394 scenes: &[Scene],
395 scene_index: usize,
396 tick: usize,
397 total_ticks: usize,
398 accent: Color,
399) {
400 draw_panel(screen, area, "STORY BUS // TELEMETRY", accent);
401 let bus_w = area.w.saturating_sub(4).max(1);
402 let scene_count = scenes.len().max(1);
403 for (index, scene) in scenes.iter().enumerate() {
404 let x0 = area.x + 2 + index * bus_w / scene_count;
405 let x1 = area.x + 2 + (index + 1) * bus_w / scene_count;
406 let active = index == scene_index;
407 for x in x0..x1.min(area.x + area.w.saturating_sub(2)) {
408 put_cell(
409 screen,
410 x,
411 area.y + 2,
412 if active { '=' } else { '-' },
413 if active {
414 scene.accent
415 } else {
416 Color::rgb(58, 70, 82)
417 },
418 Some(Color::rgb(7, 12, 18)),
419 active,
420 !active,
421 );
422 }
423 }
424 let global = tick as f32 / total_ticks.max(1) as f32;
425 draw_progress_strip(
426 screen,
427 Rect {
428 x: area.x + 2,
429 y: area.y + 4,
430 w: bus_w,
431 h: 1,
432 },
433 global,
434 Color::rgb(255, 178, 76),
435 Color::rgb(42, 54, 64),
436 );
437 let logs = [
438 "pulling tendon atlas from nature://forearm",
439 "downloading leverage matrices into the gait lab",
440 "burning actuator constraints into motion plans",
441 "decrypting sensor noise into intention",
442 "simulating force lines through cable and joint",
443 "packing humanoid grace into repeatable frames",
444 ];
445 let log = logs[(tick / 18) % logs.len()];
446 put_text(
447 screen,
448 area.x + 2,
449 area.y + 5,
450 log,
451 Color::rgb(188, 210, 224),
452 Some(Color::rgb(7, 12, 18)),
453 false,
454 );
455 let pulse = Loader::with_config(
456 LoaderKind::Braided,
457 LoaderConfig::default()
458 .with_size(area.w.saturating_sub(4).max(1), 1)
459 .with_label("field bus")
460 .with_percent(false)
461 .with_gradient(
462 Gradient::new(vec![accent, Color::rgb(255, 92, 212)], 16),
463 GradientDirection::Horizontal,
464 ),
465 )
466 .frame(tick, global);
467 draw_frame(
468 screen,
469 area.x + 2,
470 area.y + area.h.saturating_sub(2),
471 &pulse,
472 area.w.saturating_sub(4),
473 1,
474 );
475}
476
477fn build_scenes(source: &str, layout: &Layout, scene_ticks: usize) -> Vec<Scene> {
478 let paragraphs = paragraphs(source);
479 let effect_w = layout.stage.w.saturating_sub(8).max(40);
480 let effect_h = layout.stage.h.saturating_sub(16).max(5).min(12);
481 let specs = [
482 (
483 "BOOT SEQUENCE",
484 "matrix rain resolves the mission",
485 EffectKind::Matrix,
486 Color::rgb(74, 255, 170),
487 0usize,
488 ),
489 (
490 "FORGE",
491 "burning the actuator constraints",
492 EffectKind::Burn,
493 Color::rgb(255, 118, 54),
494 3,
495 ),
496 (
497 "STORM SENSORS",
498 "signal and response in the weather",
499 EffectKind::Thunderstorm,
500 Color::rgb(96, 204, 255),
501 2,
502 ),
503 (
504 "LASER ETCH",
505 "leverage maps cut into the frame",
506 EffectKind::LaserEtch,
507 Color::rgb(255, 220, 92),
508 7,
509 ),
510 (
511 "GRAVITY WELL",
512 "weight and balance collapse into order",
513 EffectKind::Blackhole,
514 Color::rgb(164, 126, 255),
515 5,
516 ),
517 (
518 "ORBITAL BALANCE",
519 "rings of motion settle around intent",
520 EffectKind::Rings,
521 Color::rgb(110, 255, 224),
522 6,
523 ),
524 (
525 "SWARM CALIBRATION",
526 "many small forces become one gait",
527 EffectKind::Swarm,
528 Color::rgb(255, 112, 218),
529 4,
530 ),
531 (
532 "FIELD DEMO",
533 "fireworks over the completed machine",
534 EffectKind::Fireworks,
535 Color::rgb(255, 180, 72),
536 1,
537 ),
538 ];
539 specs
540 .iter()
541 .enumerate()
542 .map(
543 |(index, (title, subtitle, kind, accent, paragraph_index))| {
544 let paragraph = paragraphs
545 .get(*paragraph_index)
546 .or_else(|| paragraphs.get(index % paragraphs.len().max(1)))
547 .cloned()
548 .unwrap_or_else(|| source.to_string());
549 let extra = match kind {
550 EffectKind::Matrix => "Data falls until it becomes purpose.",
551 EffectKind::Burn => "The forge removes everything that does not serve motion.",
552 EffectKind::Thunderstorm => "Noise becomes weather; weather becomes signal.",
553 EffectKind::LaserEtch => "Leverage is written as geometry and timing.",
554 EffectKind::Blackhole => {
555 "Weight collapses into balance before it expands into gait."
556 }
557 EffectKind::Rings => "Joints orbit a center of intention.",
558 EffectKind::Swarm => "Many corrections gather into one quiet step.",
559 _ => "The machine disappears into the grace of the whole.",
560 };
561 let text = wrap_text(&format!("{}\n\n{}", paragraph, extra), effect_w);
562 let gradient = Gradient::new(
563 vec![*accent, accent.brighten(0.36), Color::rgb(235, 244, 250)],
564 32,
565 );
566 let config = EffectConfig::default()
567 .with_duration(scene_ticks.max(8))
568 .with_seed(700 + index as u64 * 97)
569 .with_canvas_size(effect_w, effect_h)
570 .with_gradient(gradient, GradientDirection::Diagonal);
571 let frames = Effect::with_config(*kind, text.clone(), config).frames();
572 Scene {
573 title,
574 subtitle,
575 effect: *kind,
576 accent: *accent,
577 frames,
578 quote: paragraph,
579 }
580 },
581 )
582 .collect()
583}examples/loader_lab.rs (lines 461-468)
380fn draw_hero(
381 screen: &mut Frame,
382 area: Rect,
383 pane: &Pane,
384 tick: usize,
385 focus_ticks: usize,
386 progress_seconds: u64,
387) {
388 draw_panel(screen, area, "FOCUSED TRANSFER", pane.accent);
389 let inner = Rect {
390 x: area.x + 3,
391 y: area.y + 2,
392 w: area.w.saturating_sub(6).max(1),
393 h: area.h.saturating_sub(4).max(1),
394 };
395 let title = format!("{} // {} // {}", pane.kind.name(), pane.op, pane.noun);
396 put_text_clipped(
397 screen,
398 inner.x,
399 inner.y,
400 &title,
401 inner.w,
402 pane.accent.brighten(0.2),
403 Some(Color::rgb(7, 12, 18)),
404 true,
405 );
406 let stage_y = inner.y + 2;
407 let banner_h = pane
408 .effect_frames
409 .first()
410 .map(Frame::height)
411 .unwrap_or(3)
412 .min(5);
413 let banner = &pane.effect_frames[(tick + pane.offset) % pane.effect_frames.len().max(1)];
414 draw_frame(screen, inner.x, stage_y, banner, inner.w, banner_h);
415
416 let (progress, current, cycle) = transfer_progress(pane, tick, progress_seconds);
417 let metrics_y = stage_y + banner_h + 1;
418 let metrics = format!(
419 "{} / {}{} {:>3}% speed:{}x cycle:{} ticks",
420 current.min(pane.total),
421 pane.total,
422 pane.unit,
423 progress.percent(),
424 pane.speed,
425 cycle
426 );
427 put_text_clipped(
428 screen,
429 inner.x,
430 metrics_y,
431 &metrics,
432 inner.w,
433 Color::rgb(186, 206, 218),
434 Some(Color::rgb(7, 12, 18)),
435 false,
436 );
437 draw_progress_strip(
438 screen,
439 Rect {
440 x: inner.x,
441 y: metrics_y + 1,
442 w: inner.w,
443 h: 1,
444 },
445 progress.fraction(),
446 pane.accent,
447 Color::rgb(42, 54, 64),
448 );
449
450 let loader_y = metrics_y + 3;
451 let loader_h = inner.y + inner.h - loader_y;
452 if loader_h > 0 {
453 let loader = Loader::with_config(
454 pane.kind,
455 LoaderConfig::default()
456 .with_size(inner.w, loader_h)
457 .with_label(pane.label.clone())
458 .with_unit(pane.unit)
459 .with_fraction(true)
460 .with_gradient(
461 Gradient::new(
462 vec![
463 pane.accent,
464 pane.accent.brighten(0.38),
465 Color::rgb(90, 226, 255),
466 ],
467 28,
468 ),
469 GradientDirection::Horizontal,
470 ),
471 );
472 let frame = loader.frame(tick + pane.offset, progress);
473 draw_frame(screen, inner.x, loader_y, &frame, inner.w, loader_h);
474 }
475
476 let focus_ratio = (tick % focus_ticks.max(1)) as f32 / focus_ticks.max(1) as f32;
477 draw_progress_strip(
478 screen,
479 Rect {
480 x: inner.x,
481 y: area.y + area.h - 2,
482 w: inner.w,
483 h: 1,
484 },
485 focus_ratio,
486 Color::rgb(255, 178, 72),
487 Color::rgb(42, 54, 64),
488 );
489}
490
491fn draw_lanes(
492 screen: &mut Frame,
493 area: Rect,
494 panes: &[Pane],
495 active: usize,
496 tick: usize,
497 progress_seconds: u64,
498) {
499 draw_panel(screen, area, "LIVE LANES", Color::rgb(92, 204, 255));
500 let count = lane_count(area.h);
501 let lane_h = area.h.saturating_sub(2).max(1) / count.max(1);
502 for lane in 0..count {
503 let index = (active + lane + 1) % panes.len();
504 let pane = &panes[index];
505 let y = area.y + 2 + lane * lane_h;
506 let rect = Rect {
507 x: area.x + 2,
508 y,
509 w: area.w.saturating_sub(4).max(1),
510 h: lane_h.saturating_sub(1).max(1),
511 };
512 draw_lane(screen, rect, pane, tick, progress_seconds);
513 }
514}
515
516fn draw_lane(screen: &mut Frame, area: Rect, pane: &Pane, tick: usize, progress_seconds: u64) {
517 if area.h == 0 || area.w == 0 {
518 return;
519 }
520 let (progress, current, _) = transfer_progress(pane, tick, progress_seconds);
521 let title = format!("{:02} {} {}", pane.index + 1, pane.kind.name(), pane.op);
522 put_text_clipped(
523 screen,
524 area.x,
525 area.y,
526 &title,
527 area.w,
528 pane.accent,
529 Some(Color::rgb(7, 12, 18)),
530 true,
531 );
532 if area.h > 1 {
533 let metrics = format!(
534 "{} {} / {}{} {}x",
535 pane.label,
536 current.min(pane.total),
537 pane.total,
538 pane.unit,
539 pane.speed
540 );
541 put_text_clipped(
542 screen,
543 area.x,
544 area.y + 1,
545 &metrics,
546 area.w,
547 Color::rgb(180, 198, 210),
548 Some(Color::rgb(7, 12, 18)),
549 false,
550 );
551 }
552 if area.h > 2 {
553 let loader_h = area.h - 2;
554 let loader = Loader::with_config(
555 pane.kind,
556 LoaderConfig::default()
557 .with_size(area.w, loader_h)
558 .with_label(pane.label.clone())
559 .with_unit(pane.unit)
560 .with_percent(true)
561 .with_fraction(false)
562 .with_gradient(
563 Gradient::new(vec![pane.accent, Color::rgb(90, 226, 255)], 18),
564 GradientDirection::Horizontal,
565 ),
566 );
567 let frame = loader.frame(tick + pane.offset, progress);
568 draw_frame(screen, area.x, area.y + 2, &frame, area.w, loader_h);
569 }
570}
571
572fn draw_catalog(
573 screen: &mut Frame,
574 area: Rect,
575 panes: &[Pane],
576 active: usize,
577 tick: usize,
578 progress_seconds: u64,
579) {
580 draw_panel(
581 screen,
582 area,
583 "ALL LOADERS - VISIBLE CATALOG",
584 Color::rgb(255, 178, 72),
585 );
586 let cell_w = 11usize;
587 let cols = (area.w.saturating_sub(4) / cell_w).max(1);
588 for (index, pane) in panes.iter().enumerate() {
589 let col = index % cols;
590 let row = index / cols;
591 let x = area.x + 2 + col * cell_w;
592 let y = area.y + 2 + row;
593 if y >= area.y + area.h.saturating_sub(1) {
594 break;
595 }
596 let (progress, _, _) = transfer_progress(pane, tick, progress_seconds);
597 let marker = match (progress.percent() / 12).min(8) {
598 0 => '.',
599 1 | 2 => ':',
600 3 | 4 => '=',
601 5 | 6 => '#',
602 _ => '*',
603 };
604 let mut text = format!("{}{}", marker, pane.kind.name());
605 text.truncate(cell_w.saturating_sub(1));
606 let in_lanes = (1..=lane_count(999)).any(|offset| (active + offset) % panes.len() == index);
607 let fg = if index == active {
608 pane.accent.brighten(0.25)
609 } else if in_lanes {
610 Color::rgb(126, 194, 255)
611 } else {
612 Color::rgb(122, 140, 154)
613 };
614 put_text_clipped(
615 screen,
616 x,
617 y,
618 &text,
619 cell_w.saturating_sub(1),
620 fg,
621 Some(Color::rgb(7, 12, 18)),
622 index == active,
623 );
624 }
625}
626
627fn draw_footer(
628 screen: &mut Frame,
629 area: Rect,
630 active: usize,
631 total: usize,
632 tick: usize,
633 focus_ticks: usize,
634 manual_focus: bool,
635 accent: Color,
636) {
637 fill_rect(screen, area, Some(Color::rgb(5, 10, 16)));
638 for x in area.x..area.x + area.w {
639 put_cell(
640 screen,
641 x,
642 area.y,
643 '-',
644 Color::rgb(46, 70, 88),
645 Some(Color::rgb(5, 10, 16)),
646 false,
647 true,
648 );
649 }
650 put_text(
651 screen,
652 area.x + 2,
653 area.y + 1,
654 "<<<",
655 accent,
656 Some(Color::rgb(5, 10, 16)),
657 true,
658 );
659 put_text_right(
660 screen,
661 area.x + area.w.saturating_sub(3),
662 area.y + 1,
663 ">>>",
664 accent,
665 Some(Color::rgb(5, 10, 16)),
666 true,
667 );
668 let mode = if manual_focus { "MANUAL" } else { "AUTO" };
669 let text = format!(
670 "focus {:02}/{:02} mode:{} arrows/h-l next loader a auto q quit",
671 active + 1,
672 total,
673 mode
674 );
675 put_text(
676 screen,
677 area.x + 8,
678 area.y + 1,
679 &text,
680 Color::rgb(186, 206, 218),
681 Some(Color::rgb(5, 10, 16)),
682 false,
683 );
684 let ratio = (tick % focus_ticks.max(1)) as f32 / focus_ticks.max(1) as f32;
685 draw_progress_strip(
686 screen,
687 Rect {
688 x: area.x + 8,
689 y: area.y + 2,
690 w: area.w.saturating_sub(16),
691 h: 1,
692 },
693 ratio,
694 accent,
695 Color::rgb(42, 54, 64),
696 );
697}
698
699fn lane_count(height: usize) -> usize {
700 if height >= 34 {
701 6
702 } else if height >= 25 {
703 5
704 } else if height >= 17 {
705 4
706 } else {
707 3
708 }
709}
710
711fn transfer_progress(
712 pane: &Pane,
713 tick: usize,
714 progress_seconds: u64,
715) -> (LoaderProgress, u64, usize) {
716 let cycle = ((progress_seconds as usize * 10) / pane.speed.max(1)).max(18);
717 let progress_tick = (tick + pane.offset) % cycle;
718 let fraction = progress_tick as f32 / cycle.saturating_sub(1).max(1) as f32;
719 let current = (pane.total as f32 * fraction).round() as u64;
720 (
721 LoaderProgress::from_counts(current.min(pane.total), pane.total),
722 current,
723 cycle,
724 )
725}
726
727fn build_panes(effect_width: usize, effect_height: usize, focus_ticks: usize) -> Vec<Pane> {
728 let ops = [
729 ("DOWNLOAD", "tendon-atlas", "MB"),
730 ("UPLOAD", "field-manifest", "MB"),
731 ("SYNC", "sensor-mesh", "pkt"),
732 ("VERIFY", "leverage-map", "chk"),
733 ("STREAM", "gait-vectors", "vec"),
734 ("FORGE", "actuator-curve", "N"),
735 ("DECRYPT", "intent-bus", "blk"),
736 ("MIRROR", "joint-state", "msg"),
737 ("PACK", "motion-plan", "ops"),
738 ("RELAY", "balance-loop", "hz"),
739 ("CALIBRATE", "cable-stack", "mm"),
740 ("ARCHIVE", "field-log", "MB"),
741 ];
742 let effects = [
743 EffectKind::Matrix,
744 EffectKind::Burn,
745 EffectKind::Decrypt,
746 EffectKind::Thunderstorm,
747 EffectKind::LaserEtch,
748 EffectKind::Blackhole,
749 EffectKind::Rings,
750 EffectKind::Swarm,
751 EffectKind::Fireworks,
752 EffectKind::Slice,
753 EffectKind::Waves,
754 EffectKind::Wipe,
755 ];
756 let palette = [
757 Color::rgb(74, 255, 170),
758 Color::rgb(255, 118, 54),
759 Color::rgb(92, 204, 255),
760 Color::rgb(255, 216, 92),
761 Color::rgb(172, 130, 255),
762 Color::rgb(255, 112, 218),
763 Color::rgb(112, 236, 255),
764 Color::rgb(255, 178, 72),
765 ];
766 let speeds = [1, 2, 3, 4, 5, 7, 9, 11];
767
768 LoaderKind::all()
769 .iter()
770 .enumerate()
771 .map(|(index, kind)| {
772 let (op, noun, unit) = ops[index % ops.len()];
773 let effect_kind = effects[(index * 5 + 3) % effects.len()];
774 let accent = palette[index % palette.len()];
775 let label = format!("{} {}", op.to_ascii_lowercase(), noun);
776 let effect_text = format!("{} {}\n{}", op, kind.name(), noun).to_ascii_uppercase();
777 let effect = Effect::with_config(
778 effect_kind,
779 effect_text,
780 EffectConfig::default()
781 .with_duration(focus_ticks)
782 .with_seed(991 + index as u64 * 37)
783 .with_canvas_size(effect_width, effect_height.max(1))
784 .with_gradient(
785 Gradient::new(
786 vec![accent, accent.brighten(0.32), Color::rgb(230, 244, 250)],
787 20,
788 ),
789 GradientDirection::Diagonal,
790 ),
791 );
792 Pane {
793 index,
794 kind: *kind,
795 op,
796 noun,
797 label,
798 unit,
799 total: 128 + ((index as u64 * 97) % 2048),
800 speed: speeds[index % speeds.len()],
801 offset: index * 17,
802 accent,
803 effect_kind,
804 effect_frames: effect.frames(),
805 }
806 })
807 .collect()
808}pub fn with_loop( stops: impl Into<Vec<Color>>, steps: usize, looped: bool, ) -> Self
pub fn terminal_text_default() -> Self
pub fn stops(&self) -> &[Color]
pub fn spectrum(&self) -> &[Color]
pub fn color_at(&self, fraction: f32) -> Color
pub fn coordinate_color( &self, x: usize, y: usize, width: usize, height: usize, direction: GradientDirection, ) -> Color
Trait Implementations§
impl Eq for Gradient
impl StructuralPartialEq for Gradient
Auto Trait Implementations§
impl Freeze for Gradient
impl RefUnwindSafe for Gradient
impl Send for Gradient
impl Sync for Gradient
impl Unpin for Gradient
impl UnsafeUnpin for Gradient
impl UnwindSafe for Gradient
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more