Skip to main content

Embeds & Components

The Discord bot uses Heimdall's brand colors for embeds and provides builders for common UI patterns.

Heimdall Color Scheme

// src/utils/colors.rs

use serenity::model::Color;

/// Heimdall brand colors for Discord embeds
pub mod colors {
use serenity::model::Color;

/// Primary brand color - Mint green (#14f5bf)
pub const BRAND: Color = Color::new(0x14f5bf);

/// Error color - Red (#ff5859)
pub const ERROR: Color = Color::new(0xff5859);

/// Warning color - Amber (#f59e0b)
pub const WARNING: Color = Color::new(0xf59e0b);

/// Info color - Blue (#3b82f6)
pub const INFO: Color = Color::new(0x3b82f6);

/// Success color - Green (#10b981)
pub const SUCCESS: Color = Color::new(0x10b981);

/// Neutral color - Gray (#9ca3af)
pub const NEUTRAL: Color = Color::new(0x9ca3af);
}

/// Raw u32 values for non-Serenity usage
pub mod colors_raw {
pub const BRAND: u32 = 0x14f5bf;
pub const ERROR: u32 = 0xff5859;
pub const WARNING: u32 = 0xf59e0b;
pub const INFO: u32 = 0x3b82f6;
pub const SUCCESS: u32 = 0x10b981;
pub const NEUTRAL: u32 = 0x9ca3af;
}

Embed Builders

Standard Heimdall Embed

// src/embeds/builders.rs

use serenity::all::{CreateEmbed, CreateEmbedFooter};
use crate::utils::colors;
use rust_i18n::t;

/// Build a standard Heimdall embed with brand color
pub fn heimdall_embed() -> CreateEmbed {
CreateEmbed::new()
.color(colors::BRAND)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

/// Build an error embed
pub fn error_embed(title: &str, description: &str) -> CreateEmbed {
CreateEmbed::new()
.color(colors::ERROR)
.title(title)
.description(description)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

/// Build a success embed
pub fn success_embed(title: &str, description: &str) -> CreateEmbed {
CreateEmbed::new()
.color(colors::SUCCESS)
.title(title)
.description(description)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

/// Build a warning embed
pub fn warning_embed(title: &str, description: &str) -> CreateEmbed {
CreateEmbed::new()
.color(colors::WARNING)
.title(title)
.description(description)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

/// Build an info embed
pub fn info_embed(title: &str, description: &str) -> CreateEmbed {
CreateEmbed::new()
.color(colors::INFO)
.title(title)
.description(description)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

Usage Examples

use crate::embeds::builders::*;
use serenity::all::CreateMessage;

// Success message
let embed = success_embed("Action Completed", "The user has been updated successfully.");
msg.channel_id.send_message(ctx, CreateMessage::new().embed(embed)).await?;

// Error message
let embed = error_embed("Error", "Failed to process the request.");
msg.channel_id.send_message(ctx, CreateMessage::new().embed(embed)).await?;

// Custom embed with fields
let embed = heimdall_embed()
.title("Bot Information")
.field("Version", env!("CARGO_PKG_VERSION"), true)
.field("Uptime", "2 hours", true)
.field("Guilds", "150", true)
.thumbnail("https://example.com/logo.png");

msg.channel_id.send_message(ctx, CreateMessage::new().embed(embed)).await?;

Embed Templates

Pre-defined templates for common use cases:

// src/embeds/templates.rs

use serenity::all::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter};
use crate::utils::colors;
use rust_i18n::t;

/// User profile embed
pub fn user_profile_embed(
username: &str,
avatar_url: Option<&str>,
fields: Vec<(&str, String, bool)>,
) -> CreateEmbed {
let mut embed = CreateEmbed::new()
.color(colors::BRAND)
.author(CreateEmbedAuthor::new(username));

if let Some(url) = avatar_url {
embed = embed.thumbnail(url);
}

for (name, value, inline) in fields {
embed = embed.field(name, value, inline);
}

embed.footer(CreateEmbedFooter::new(t!("embeds.powered_by")))
}

/// Help embed for commands
pub fn help_embed(commands: Vec<(&str, &str)>) -> CreateEmbed {
let mut embed = CreateEmbed::new()
.color(colors::INFO)
.title(t!("commands.help.title"));

for (name, description) in commands {
embed = embed.field(name, description, false);
}

embed.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

/// Confirmation embed
pub fn confirmation_embed(title: &str, description: &str) -> CreateEmbed {
CreateEmbed::new()
.color(colors::WARNING)
.title(format!("⚠️ {}", title))
.description(description)
.footer(CreateEmbedFooter::new(t!("embeds.footer")))
}

Button Builders

// src/components/buttons.rs

use serenity::all::{CreateButton, ButtonStyle, CreateActionRow};

/// Primary action button
pub fn primary_button(custom_id: &str, label: &str) -> CreateButton {
CreateButton::new(custom_id)
.label(label)
.style(ButtonStyle::Primary)
}

/// Secondary action button
pub fn secondary_button(custom_id: &str, label: &str) -> CreateButton {
CreateButton::new(custom_id)
.label(label)
.style(ButtonStyle::Secondary)
}

/// Success button
pub fn success_button(custom_id: &str, label: &str) -> CreateButton {
CreateButton::new(custom_id)
.label(label)
.style(ButtonStyle::Success)
}

/// Danger button
pub fn danger_button(custom_id: &str, label: &str) -> CreateButton {
CreateButton::new(custom_id)
.label(label)
.style(ButtonStyle::Danger)
}

/// Link button (opens URL)
pub fn link_button(url: &str, label: &str) -> CreateButton {
CreateButton::new_link(url)
.label(label)
}

/// Create a row of buttons
pub fn button_row(buttons: Vec<CreateButton>) -> CreateActionRow {
CreateActionRow::Buttons(buttons)
}

Button Usage

use crate::components::buttons::*;
use serenity::all::CreateMessage;

// Confirmation buttons
let confirm = success_button("confirm_action", "Confirm");
let cancel = danger_button("cancel_action", "Cancel");

let msg = CreateMessage::new()
.content("Are you sure?")
.components(vec![button_row(vec![confirm, cancel])]);

channel.send_message(ctx, msg).await?;

Select Menu Builders

// src/components/selects.rs

use serenity::all::{
CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption,
CreateActionRow,
};

/// Create a string select menu
pub fn string_select(
custom_id: &str,
placeholder: &str,
options: Vec<(&str, &str)>,
) -> CreateSelectMenu {
let options: Vec<CreateSelectMenuOption> = options
.into_iter()
.map(|(label, value)| CreateSelectMenuOption::new(label, value))
.collect();

CreateSelectMenu::new(custom_id, CreateSelectMenuKind::String { options })
.placeholder(placeholder)
}

/// Create a user select menu
pub fn user_select(custom_id: &str, placeholder: &str) -> CreateSelectMenu {
CreateSelectMenu::new(custom_id, CreateSelectMenuKind::User)
.placeholder(placeholder)
}

/// Create a role select menu
pub fn role_select(custom_id: &str, placeholder: &str) -> CreateSelectMenu {
CreateSelectMenu::new(custom_id, CreateSelectMenuKind::Role)
.placeholder(placeholder)
}

/// Create a select menu row
pub fn select_row(select: CreateSelectMenu) -> CreateActionRow {
CreateActionRow::SelectMenu(select)
}

Select Menu Usage

use crate::components::selects::*;
use serenity::all::CreateMessage;

// Language selection
let select = string_select(
"language_select",
"Select a language",
vec![
("English", "en"),
("German", "de"),
],
);

let msg = CreateMessage::new()
.content("Choose your language:")
.components(vec![select_row(select)]);

channel.send_message(ctx, msg).await?;

Handling Component Interactions

// src/interactions/components.rs

use serenity::all::{
ComponentInteraction, CreateInteractionResponse,
CreateInteractionResponseMessage,
};

pub async fn handle_component(
ctx: &Context,
interaction: ComponentInteraction,
) -> Result<(), Error> {
match interaction.data.custom_id.as_str() {
"confirm_action" => {
interaction.create_response(ctx, CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Action confirmed!")
.ephemeral(true)
)).await?;
}
"cancel_action" => {
interaction.create_response(ctx, CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Action cancelled.")
.ephemeral(true)
)).await?;
}
"language_select" => {
if let ComponentInteractionDataKind::StringSelect { values } = &interaction.data.kind {
let selected = &values[0];
rust_i18n::set_locale(selected);
// Save to user preferences...
}
}
_ => {}
}

Ok(())
}

Message Builders

Safe text construction using Serenity's MessageBuilder:

use serenity::utils::MessageBuilder;

// Safe mention
let content = MessageBuilder::new()
.push("Hello, ")
.mention(&user)
.push("! Welcome to ")
.push_bold_safe(&guild_name)
.push(".")
.build();

// Code block
let content = MessageBuilder::new()
.push("Here's the code:\n")
.push_codeblock_safe(code, Some("rust"))
.build();

// Quote
let content = MessageBuilder::new()
.quote_rest()
.push(quote_text)
.build();